1. 程式人生 > 實用技巧 >質數篩

質數篩

暴力篩
程式碼:

1 bool check_prime(int n)
2 {
3   if(n==1) return 0;
4   for(int i=1;i<=n;i++)
5   if(n%i==0) return 1;
6   return 0;
7 }

六篩法
程式碼:

 1 bool check_prime(int n)
 2 {
 3     if(n==1) return 0;//1既不是質數也不是合數 
 4     if(n==2||n==3) return 1;//2,3不是6兩側的數但他是質數 
 5     if(n%6!=1&&n%6!=5) return 0
;//質數只能是6兩側 6 int x=sqrt(n);//篩到根號n 7 for(int i=5;i<=x;i++) 8 if(n/i==0||n/(i+2)==0) return 0;//如果他還有約數 9 return 1;//若沒有約數 10 }

引理:算術基本定理——任何一個大於1的自然數N,如果N不為質數,那麼N可以唯一分解成有限個質數的乘積
所以只需要n無法被質數整除n即為質數
由於6兩側的數不是2和3的倍數所以不需要用2和3來篩
也不需要用6n+2,3,4來篩因為這些都是2和3的倍數,如果該數能被篩掉則改該數也能被2和3整除,不符合上述條件

線性篩 (也叫尤拉篩)
程式碼:

int primes[N], cnt; // primes[]儲存所有素數
bool st[N]; // st[x]儲存x是否被篩掉
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;//如果沒有沒有被篩掉就存入primes 
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            
if (i % primes[j] == 0) break; } } }

引理:算術基本定理——任何一個大於1的自然數N,如果N不為質數,那麼N可以唯一分解成有限個質數的乘積
顯然,在所有大於0的自然數中,除了質數就是合數.要求質數,只需要”篩”去所有的合數即可.如何篩去合數
這裡就應用到了算術基本定理.關於合數,這裡我們需要注意兩點:
(1).所有合數都要篩到
(2).不能有重複篩選,否則無法達到線上性時間內的運行了
要做到第一點,根據算術基本定理:n=q*m(q表示最小質數,m=N/q,顯然m>=q),對N內的m和q進行列舉
這樣我們就保證了能列舉N以內的所有整數,然而我們還不能保證列舉的合數不重複。

重點:先來思考一下為什麼會有重複,由於對於任意一個n=m*q,m和q是相對的兩個狀態,m大了q就變小,m小了q就變大。

重點:設n=m1*q1=m2*q2,q1<=q2<=m2<=m1我們這裡需要保證當且僅當在q1是最小質因數時能求解,也就是去除q2和m2的情況.

重點:因為q1與q2互質,且q1<q2,則有m2%q1=0;由於每一個i值都是不同的因此我們認為當前的i值符合m1的條件因此臨界條件為

重點:q1=q2,m2=m1,此時q1再增大時就有危險因為此時不符合除去q2和m2的條件

所以關鍵步驟來了: if(i%peimes[j]==0) break;
當i是primes[j]的倍數時直接跳出迴圈,設i=primes[j]*t,此時有i*primes[j+1]=primes[j]*t*primes[j+1]
可以看到此時的最小質數時primes[j],為了避免與當m=primes[j+1]*t時有重複,這裡可以通過break跳過計算
此外還需注意:要保證m從2開始,因為1*q=1且i%1==0;