如果只是對一個整數進行素性測試的只要o(√n)的復雜度便可以判定,藍而如果是n個呢(n<=1000)照樣可以,那如果100000個呢?對於普通的o(n√n)根本跑不動,因此我們必須尋找更加高效的算法,常用的篩選方法有埃氏篩法, 區間篩法,歐拉篩法。
1.埃氏篩法
首先,我們先把2-n范圍內的數寫下來,其中最小的素數是2,那么能被2整除的數便不是素數,那么我們可以把2的倍數都划去。然后剩下的最小素數便是3,我們便把3的倍數都划去,以此類推。這樣反復操作我們就能枚舉n以內的素數。
貼上偽代碼:
1 const int N = 1000000 + 5; 2 int check[N], prime[N]; 3 4 int ptot = 0; 5 memset(check, 0, sizeof(check)); 6 for(int i = 2; i <= n; i ++){ 7 if(!check[i])prime[ptot++] = i; 8 for(int j = 2; i * j <= n; j ++) check[i * j] = 1; 9 }
但是問題又來了, 這個復雜度究竟該怎么算呢?怎么看像o(n2)呢?No No No~~
每次我們篩去每個數的整數倍像2篩了n/2個,3篩了n/3個;
因此總的復雜度約為:
哎呀!這不是發散級數嗎?這個不是發散的數啊,那復雜度不是會爆掉嗎?
別擔心,雖然它是發散級數,但是他的增長速度非常慢,來我們測試一下
看到了嗎?這個發散級數在n = 1000000的時候都沒有超過14由此可見它的復雜度很小,近似等效成o(nloglogn),而對我們這些搞ACM的人來講他大致看成線性的都無妨。
2.區間篩法
設區間[a,b)表示a<=x<b,的素數所構成的集合。
我們很容易知道b只要不能整除[2,√b]里面的素數,那么b就可以被認定為一個素數,因此,如果我們有√b里面的素數表,便可以結合埃氏篩法來判斷。
附上偽代碼:
1 const int N = 100000 + 5; 2 int check[N], prime[N]; 3 int is_prime[N]; 4 5 int ptot = 0; 6 memset(check, 0, sizeof(check)); 7 memset(is_prime, 0, sizeof(is_prime)); 8 for(int i = 2; i <= n; i ++){ 9 if(!check[i])prime[ptot++] = i; 10 for(int j = 2; i * j <= sqrt(b); j ++) check[i * j] = 1;//篩[2,√b) 11 for(int j = a/i+1; i * j < b; j ++) is_prime[i * j - a] = 0;//篩[a,b) 12 //將[a, b)的數,移到[0, b - a)可節省空間 13 }
3.歐拉篩法
這便是重頭戲,真真正正的o(n)的復雜度,不說什么,直接上代碼。
1 const int N = 1000000 + 5; 2 int prime[N], check[N]; 3 4 memset(prime, 0, sizeof(prime)); 5 memset(check, 0, sizeof(check)); 6 int ptot = 0; 7 for(int i = 2; i <= n; i ++){ 8 if(!check[i]) prime[ptot ++] = i; 9 for(int j = 0; j < ptot; j ++){ 10 if(prime[j] * i > n) break; 11 check[prime[j] * i] = 1; 12 if(i % prime[j] == 0) break; 13 } 14 }
這個貌似只是做了少許改進了吧,貌似就改進了第12行啊,聰明,藍而就是因為這一行,導致了他的復雜度變成完美的線性, 設合數的最小質因數為p,則當它遍歷到這個素數的時候因為第12行二跳出循環,這樣直接導致了每個合數只能被它的最小質因數給刪去。
下面給出證明:
設合數n的最小質因數為p,另一個比它大的質因數為p1,設 n = p1 * m1 = p * m,我們很容易就可以發現j循環到質因數p的時候合數n第一次被標記(若循環到p之前已經跳出循環則說明有比p更小的質因數),若也被p1標記,則是在這之前(因為p1 > p,所以m1 < m),考慮i循環到m1,注意到n = p*m=p1*m1且p,p1為不同的質數,因此p|m1,所以當j循環到質數p后就結束,不會循環到p1.這就說明了不會被p1篩去,說明合數只能被它最小的素數給刪去。(很神奇吧!)

