8. 篩法概述
全名為 埃拉托斯特尼(Eratosthenes,古希臘數學家) 篩法,這裡只概述其大致概念。
假設要找出 1~40(別太大,不然不好說明)內所有質數,先從 1 寫到 40
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
接著一個數一個數判斷,
(1) 1 非質數也非合數,直接先畫掉
(2) 2 沒被畫掉,是質數,把 2 的倍數全都劃掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
(3) 3 沒被劃掉,是質數,把3的倍數也全劃掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
(4) 4 被劃掉了,不理它,繼續往下看
(5) 5 沒被劃掉,它是質數,再把 5 的倍數全都劃掉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
(6) 6已被劃掉,不理它,繼續看
(7) 7 沒被劃掉,它是質數,再把 7 的倍數全劃掉。
(8) 由於 40 的平方根介於 6 與 7 之間,所以上面動作做到 7 即可,接下來剩下的數全都是質數。
看得出來有 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37。
9. 篩法 Coding 注意事項
篩法最大優勢是在於找一定數以內的所有質數,在 coding 時要注意,
動態陣列應用 unsigned char / char / bool,別用 int
上面三個資料型態都吃 1 byte,但 int 卻吃 4 bytes,在做篩法時之 table 只是要紀錄 "是" 或 "不是" 質數,
因此沒必要開那麼大的空間來吃記憶體空間,這樣只會使得 N 之上限值沒那麼大!
( coder 於 OS 可配置之記憶體空間僅有 user space ,XP 為 2GB / 3GB)
這部份單純之 coding 如下所示
void* se_basic(int Max, int* cnt) { // total size: Max+1; un size: 0, 1;Max+1-2 = Max-1 register int PrimeCnt=Max-1; register int i, j, end; byte* p=(byte*)malloc(Max+1); if(p==NULL){ *cnt=0; return NULL; } memset(p, 1, Max+1),p[0]=p[1]=0; end = (int)ceil(sqrt((double)Max)); for(i=2; i<=end; ++i){ if(p[i]) for(j=i+i; j<=Max; j+=i) PrimeCnt-=p[j], p[j]=0; } *cnt = PrimeCnt; return p; }
上述是最基本型的篩法。
10. 套用 2n /6n 法則
即使是篩法也可應用 2n / 6n 法則。即 2 / 2.3 之倍數都不去判斷,試想一下,2 這個數的倍數就佔了整張表之一半,一開始要刪時便花了近一半的時間,不合效率,於是將 2n 法則套近篩法裡面。
// ================================== // check odd only void* se_2n(int Max, int* cnt) { // total size: Max+1; un size: 0, 1; 2 is prime; Max+1-2+1 = Max register int PrimeCnt= (Max>>1)+(Max&1); register int i, j, end; byte* p=(byte*)malloc(Max+1); if(p==NULL){ *cnt=0; return NULL; } memset(p, 1, Max+1),p[0]=p[1]=0; end = (int)ceil(sqrt((double)Max)); for(i=3; i<=end; i+=2){ if(p[i]) for(j=3*i; j<=Max; j+=(i<<1)) PrimeCnt-=p[j], p[j]=0; } *cnt = PrimeCnt; return p; }
2n 可以套樣,一樣的,6n 、30n 、210n 也可以套用,其實用到 6n 已夠了,210n 實在太麻煩且也沒比較快,30n code 一起附上
void* se_sqrt_6n(int Max, int* cnt) { // total size: Max+1; un size: 0, 1; 2 is prime; Max+1-2+1 = Max register int PrimeCnt; register int i, j, end, gap1; byte* p=(byte*)malloc(Max+1); if(p==NULL){ *cnt=0; return NULL; } memset(p, 1, Max+1),p[0]=p[1]=0; end = (int)ceil(sqrt((double)Max)); for(gap1=4, i=5; i<=end;gap1^=6, i+=gap1){ if(p[i]){ for(j=i*i; j<=Max; j+=i+i) p[j]=0; } } for(i=5, gap1=4, PrimeCnt=2; i<=Max; gap1^=6, i+=gap1 ){ PrimeCnt += p[i]; } *cnt = PrimeCnt; return p; } void* se_sqrt_30n(int Max, int* cnt) { // total size: Max+1; un size: 0, 1; 2 is prime; Max+1-2+1 = Max register int PrimeCnt; register int i, j, k, end; byte* p=(byte*)malloc(Max+1); uint a_s[] = {6,4,2,4,2,4,6,2};//step uint a_bound = sizeof(a_s)/sizeof(a_s[0])-1; if(p==NULL){ *cnt=0; return NULL; } memset(p, 1, Max+1),p[0]=p[1]=0; end = (int)ceil(sqrt((double)Max)); for(k=0, i=7; i<=end; i+=a_s[k]){ if(p[i]){ for(j=i*i; j<=Max; j+=i+i) p[j]=0; } (k==a_bound)?(k=0): (++k); } for(PrimeCnt=3, i=7, k=0; i<=Max; i+=a_s[k]){ PrimeCnt += p[i]; (k==a_bound)?(k=0): (++k); } *cnt = PrimeCnt; return p; }
11. 將不必要空間拿掉
以 2n 為例,事實上有一半空間用不到,於是把這一半空間都拿掉
// ================================== // using half space for 2n rule void* se_2n_comp(int Max, int* cnt) { register int pSize = (Max>>1) + (Max&1); register int PrimeCnt = pSize; register int i, j, step, end; byte* p=(byte*)malloc(PrimeCnt); memset(p, 1, PrimeCnt),p[0]=0; i = (int)(ceil(sqrt((double)(Max)))); end = (i - (!(i&1))) >> 1; for(i=1; i<=end; ++i){ if(p[i]){ // number = (2*i+1) // number^2 = (2*i+1) * (2*i+1) = 4*i*i+4*i+1; // number^2 of index = number/2 = 2*i*i + 2*i = 2*i*(i+1) j = i*(i+1)<<1; // index of step step = (i<<1)+1; for(; j< pSize; j+=step) { PrimeCnt-=p[j], p[j]=0; } } } *cnt=PrimeCnt; // show table return p; }
12. 以 bitwise 方式操作
上述已拿掉一半之空間了,但仍不夠!要存一個數是不是質數,只要用 1 bit 去存便可,根本就用不到 1 bytes,這樣只是浪費其它 7 bits 而已,以 bitwise 操作較隱晦,下列程式碼以 2n 為例作參考
#define IndexToNum2(index )(1+((index)<<1)) #define NumToIndex2(number)((number-1)>>1 ) #define SET_BIT(n, bit)((n)|=(0x01 <<(bit))) #define CLR_BIT(n, bit)((n)&=~(0x01 <<(bit))) #define CHK_BIT(n, bit)(((n)>>(bit))&1) void* se_2n_Hcomp(int Max, int* cnt) { // seven register variable. register int i, j, Check_end= (Max>>1) + (Max&1); /* check bound to resize */ register int pSize = (Max >> 6) + 1 - ( (Max&63)==0); register int PrimeCnt = Check_end; register int end, step; uint* p = (uint*)malloc(sizeof(uint)*pSize); memset(p, 0xffffffff, sizeof(uint)*pSize), p[0]=0xfffffffe; i = (int)ceil(sqrt((double)Max)); /* cal sqrt(N) */ end = NumToIndex2(i); /* cal the last check index */ for(i=1; i<=end; ++i){ if( CHK_BIT(p[i>>5], (i&31))) { j = i*(i+1)<<1; step = (i<<1)+1; for(; j<Check_end; j+=step){ PrimeCnt -= ( CHK_BIT(p[j>>5], j&31)); CLR_BIT(p[j>>5], j&31); } j-=step; // printf("%d\n", IndexToNum2(j)); // check bound. } } *cnt = PrimeCnt; return p; }
這些都是經由 bit 運算轉換去推出來的寫法,同時這種操作方式也會使得速度加快,而且快不少。
以 6n 方式進行 bitwise 操作,這部份實不容易,但網路上仍可找到這部份文章說明,於此便不贅述。
* 線性篩法
上述的篩法是套用了 2n 法則,所有 2 之倍數都不去砍,減少 polling 時間;同樣也有 6n 法則,所有 2 與 3 之倍數都不去砍,又減少了 polling 時間,但這二個方法都會有重覆刪除的現象。線性篩法優點便可保證每個要刪除的數字都只刪除一次,速度為線性,故稱線性篩法。但缺點是需要額外的記憶體空間存質數表。換而言之,如果可定址空間只在 2GB,且欲篩選範圍甚大,此時便不適用線性篩法,因質數表所需空間甚大。 這次以反邏輯方式撰之,基礎型的線性篩法大致如下
#include <stdio.h> #include <math.h> #define N 100 int Prime[N]={0}; char p[N]={0}; int main() { int i, j, end, Cnt=0; p[0]=p[1]=1; for(i=2; i!=N; ++i){ if(p[i]==0) Prime[Cnt++]=i; for(j=0; j!=Cnt && i*Prime[j]<=N; ++j) p[i*Prime[j]]=1; } Cnt=0; for(i=0; i!=N; ++i) if(p[i]==0) printf("%d", i), ++Cnt; printf("\nCnt: %d\n", Cnt); return 0; }
線性篩法過程中,甚至還可以再套用 2n 、6n 法則
#include <stdio.h> #define N 100 int Prime[N]={0}; char p[N]={0}; int main() { int i, j, end, gap, Cnt; p[0]=p[1]=p[4]=1; for(gap=2, i=5; i<N; i+=gap, gap^=6){ if(p[i]==0) Prime[Cnt++]=i; for(j=0; j!=Cnt && i*Prime[j]<=N; ++j) p[i*Prime[j]]=1; } Cnt=0; for(i=0; i!=N; ++i) if(i==2 || i==3 || (p[i]==0 && i%2!=0 && i%3!=0)) printf("%d", i), ++Cnt; printf("\nCnt: %d\n", Cnt); return 0; }
甚至可再進一步用 bit 存資訊、以 bitwise 操作,於此便不再示範。
參考資料
wiki - Mersenne prime
wiki - Fermat number
wiki - Carmichael number
wiki - Modular exponentialiation
wiki - Montgomery reduction
wiki - Fermat primality test
wiki - Miller-Rabin primality test
wiki - Lucas Lehmer test for Mersenne numbers
wiki - AKS primality test
wiki - Prime-counting function
wiki - Euler's totient function
留言列表