線性篩素數
轉自我的部落格,原文連結:http://blog.amaok.com/2018/prime_linear_sieve
突然更新的學習筆記。
最近做了一道在很大的範圍裡篩選出素數的神仙題,因為超時搞得很頭疼,所以想來深入瞭解一下線性篩法,提高程式執行效率。
我們常說的線性篩是指線上性時間內把素數表篩出來的過程,這裡介紹兩種篩法.
一般篩法(埃拉託斯特尼篩法):
基本思想:素數的倍數一定不是素數
實現方法:用一個長度為N+1的陣列儲存資訊(0表示素數,1表示非素數),先假設所有的數都是素數(初始化為0),從第一個素數2開始,把2的倍數都標記為非素數(置為1),一直到大於N;然後進行下一趟,找到2後面的下一個素數3,進行同樣的處理,直到最後,陣列中依然為0的數即為素數。
說明:整數1特殊處理即可。
舉個例子
我們篩前20個數
首先初始為(0代表不是素數,1代表是素數)
0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
然後從2開始我們發現2被標記為素數,我們把2的倍數全部篩掉
變為:
0 1 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
接著到3我們發現3仍然被標記,把3的倍數全部篩掉
變為:
0 1 1 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0
接著一直重複下去就得到了最後的素數表:
0 1 1 0 1 0 1 0 0 0 1 0 1 0 0 0 1 0 1 0
2 3 5 7 11 13 17 19
1 const int MAXN = 1000000; 2 void get_list() 3 { 4int i, j; 5for (i=0; i<MAXN; i++) prime[i] = 1; 6prime[0] = prime[1] = 0; 7for (i=2; i<MAXN; i++) 8{ 9if (!prime[i]) continue; 10for (j=i*2; j<MAXN; j+=i) prime[ j ] = 0; 11} 12 }//調和級數證明可得複雜度為(nlglgn),所以不能稱之為線性篩,但是它的實際執行速度也不是特別慢
下面我們來介紹一波真正的線性篩(尤拉篩法):
我們發現在上面的篩法中有的數字是多個素數的倍數,也就是說它可能會被重複計算多次,比如說6同時是2與3的倍數,它在計算時就被訪問了兩次,這樣會導致效率低下,所以在下面的演算法中我們考慮如何優化這種情況。
原理:
任何一個合數都可以表示成一個質數和一個數的乘積
假設A是一個合數,且A = x * y,這裡x也是一個合數,那麼有:
A = x * y; (假設y是質數,x合數)
x = a * b; (假設a是質數,且a < x——>>a<y) -> A = a b y = a Z (Z = b y)
即一個合數(x)與一個質數(y)的乘積可以表示成一個更大的合數(Z)與一個更小的質數(a)的乘積,那樣我們到每一個數,都處理一次,這樣處理的次數是很少的,因此可以線上性時間內得到解。
仍然按上面的例子模擬(這裡0為是素數,1為非素數,p為記錄的素數表):
初始:
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
p(empty)
然後到2的位置,把2放入素數表,做當前範圍內可以篩掉的處理(具體是怎樣的看程式碼叭):
1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
p 2 到3,把3放入素數表,繼續處理
1 0 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0
p 2 3 然後到了4,它不是個素數,也處理一下
1 0 0 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0
p 2 3 .......
然後一直搞下去,最後也能得到完整的素數表,這樣雖然看起來複雜一些,但是實際上我們發現對於每個數的處理幾乎是O(1)的。
1void get_list(){ 2for(int i=2;i<=maxn;i++){ 3if(!is_not_pr[i]) prime[++tot]=i; 4for(int j=1;j<=tot&&i*prime[j]<=maxn;j++){ 5is_not_pr[i*prime[j]]=1;//合數標為1,同時,prime[j]是合數i*prime[j]的最小素因子 6if(i%prime[j]==0) break;//即比一個合數大的質數和該合數的乘積可用一個更大的合數和比其小的質數相乘得到 7} 8} 9 }
所以說有了這兩個東西后a掉那道題就很輕鬆了_(:зゝ∠)_