chuanbindeng 的 素數推斷演算法
關於素數的演算法是資訊學競賽和程式設計競賽中常考的數論知識,在這裡我跟大家講一下尋找一定範圍內素數的幾個演算法。看了以後相信
對大家一定有幫助。
正如大家都知道的那樣,一個數 n 假設是合數,那麼它的全部的因子不超過sqrt(n)--n的開方,那麼我們能夠用這個性質用最直觀的方法
來求出小於等於n的全部的素數。
num = 0;
for(i=2; i<=n; i++)
{ for(j=2; j<=sqrt(i); j++)
if( j%i==0 ) break;
if( j>sqrt(i) ) prime[num++] = i; //這個prime[]是int型,跟以下講的不同。
}
這就是最一般的求解n以內素數的演算法。複雜度是o(n*sqrt(n)),假設n非常小的話,這樣的演算法(事實上這是不是演算法我都懷疑,沒有水平。當然沒
接觸過程式競賽之前我也僅僅會這一種求n以內素數的方法。-_-~)不會耗時非常多.
可是當n非常大的時候,比方n=10000000時,n*sqrt(n)>30000000000,數量級相當大。在一般的機子它不是一秒鐘跑不出結果,它是好幾分鐘都跑不
出結果,這可不是我瞎掰的,想鍛鍊耐心的同學最好還是試一試~。。。。
在程式設計競賽中就必需要設計出一種更好的演算法要求能在幾秒鐘甚至一秒鐘之內找出n以內的全部素數。於是就有了素數篩法。
(我表達得不清楚的話不要罵我,見到我的時候扁我一頓我不說一句話。。。)
素數篩法是這種:
1.開一個大的bool型陣列prime[],大小就是n+1就能夠了.先把全部的下標為奇數的標為true,下標為偶數的標為false.
2.然後:
for( i=3; i<=sqrt(n); i+=2 )
{ if(prime[i])
for( j=i+i; j<=n; j+=i ) prime[j]=false;
}
3.最後輸出bool陣列中的值為true的單元的下標,就是所求的n以內的素數了。
原理非常easy,就是當i是質(素)數的時候,i的全部的倍數必定是合數。假設i已經被推斷不是質數了,那麼再找到i後面的質數來把這個質
數的倍數篩掉。
一個簡單的篩素數的過程:n=30。
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
第 1 步過後2 4 ... 28 30這15個單元被標成false,其餘為true。
第 2 步開始:
i=3; 因為prime[3]=true, 把prime[6], [9], [12], [15], [18], [21], [24], [27], [30]標為false.
i=4; 因為prime[4]=false,不在繼續篩法步驟。
i=5; 因為prime[5]=true, 把prime[10],[15],[20],[25],[30]標為false.
i=6>sqrt(30)演算法結束。
第 3 步把prime[]值為true的下標輸出來:
for(i=2; i<=30; i++)
if(prime[i]) printf("%d ",i);
結果是 2 3 5 7 11 13 17 19 23 29
這就是最簡單的素數篩選法,對於前面提到的10000000內的素數,用這個篩選法能夠大大的減少時間複雜度。把一個僅僅見黑屏的演算法
優化到立竿見影,一下就得到結果。關於這個演算法的時間複雜度,我不會描寫敘述,沒看到過相似的記載。僅僅知道演算法書上如是說:前幾年比
較好的演算法的複雜度為o(n),空間複雜度為o(n^(1/2)/logn).另外還有時間複雜度為o(n/logn),但空間複雜度為O(n/(lognloglogn))的演算法。
我水平有限啦,自己分析不來。最有說服力的就是自己上機試一試。以下給出這兩個演算法的程式:
//最普通的方法:
#include<stdio.h>
#include<math.h>
#define N 10000001
int prime[N];
int main()
{
int i, j, num = 0;
for(i=2; i<N; i++)
{ for(j=2; j<=sqrt(i); j++)
if( j%i==0 ) break;
if( j>sqrt(i) ) prime[num++] = i;
}
for(i=2; i<100; i++) //因為輸出將佔用太多io時間,所以僅僅輸出2-100內的素數。能夠把100改為N
if( prime[i] )printf("%d ",i);
return 0;
}
//用了篩法的方法:
#include<stdio.h>
#include<math.h>
#define N 10000001
bool prime[N];
int main()
{
int i, j;
for(i=2; i<N; i++)
if(i%2) prime[i]=true;
else prime[i]=false;
for(i=3; i<=sqrt(N); i++)
{ if(prime[i])
for(j=i+i; j<N; j+=i) prime[i]=false;
}
for(i=2; i<100; i++)//因為輸出將佔用太多io時間,所以僅僅輸出2-100內的素數。能夠把100改為N
if( prime[i] )printf("%d ",i);
return 0;
}
裝了vc的同學上機跑一下這兩個程式試一試。這個區別,絕對是天上地下。前面那個程式絕對是n分鐘黑屏的說。
另外,對於這種篩法,還能夠進一步優化,就是bool型陣列裡面僅僅存奇數不存偶數。如定義prime[N],則0表示
3,1表示5,2表示7,3表示9...。假設prime[0]為true,則表示3時素數。prime[3]為false意味著9是合數。
這種優化不是簡單的降低了一半的迴圈時間,比方依照原始的篩法,陣列的下標就相應數。則在計算30以內素
數的時候3個步驟加起來走了15個單位時間。可是用這種優化則是這樣:
則因為僅僅存3 5 7 9 11 13 15 17 19 21 23 25 27 29,僅僅須要14個單元
第 1 步 把14個單元賦為true (每一個單元代表的數是2*i+3,如第0單元代表3,第1單元代表5...)
第 2 步開始:
i=0; 因為prime[0]=true, 把 [3], [6], [9], [12]標為false.
i=1; 因為prime[1]=true, 把 [6], [11]標為false
i=2 2*i+3>sqrt(30)演算法結束。
這樣優化以後總共僅僅走6個單位時間。
當n相當大以後這種優化效果就更加明顯,效率絕對不不過翻倍。
出了這種優化以外,另外在每一次用當前已得出的素數篩選後面的數的時候能夠一步跳到已經被判定不是素數的
數後面,這樣就降低了大量的反覆計算。(比方我們看到的,i=0與i=1時都標了[6],這個就是反覆的計算。)
我們能夠發現一個規律,那就是3(即i=0)是從下標為[3]的開始篩的,5(即i=1)是從下標為[11]開始篩的(由於[6]
已經被3篩過了)。然後假設n非常大的話,繼續篩。7(i=2)本來應該從下標為[9]開始篩,可是因為[9]被篩過了,而
[16]也已經被5(i=1)篩過了。於是7(i=2)從[23](就是2*23+3=49)開始篩。
於是外圍迴圈為i時,記憶體迴圈的篩法是從 i+(2*i+3)*(i+1)即i*(2*i+6)+3開始篩的。
這個優化也對演算法複雜度的減少起到了非常大的作用。
相比於一般的篩法,增加這兩個優化後的篩法要高效非常多。高興去的同學能夠試著自己編敲程式碼看一看效率。我這裡
有程式,須要的能夠向我要。不懂得也能夠問我。
上面的素數篩法是全部程式設計競賽隊員都必須掌握的,而後面加了兩個優化的篩法是效率非常高的演算法,是湖南大學
huicpc39同學設計的(可能是學來的,也可能是自創的。相當強悍)。在數量級更大的情況下就能夠發現一般篩法和
優化後的篩法的明顯差別。
另外,臺灣的ACMTino同學也給我介紹了他的演算法:a是素數,則下一個起點是a*a,把後面的全部的a*a+2*i*a篩掉。
這上面的全部的素數篩選的演算法都能夠再進一步化為二次篩選法,就是欲求n以內的素數,就先把sqrt(n)內的素數求
出來,用已經求得的素數來篩出後面的合數。
我把一般的篩選法的過程具體的敘述了一遍,應該都懂了吧?後面的優化過程及不同的方法,能看懂最好。不是非常難的。
相關知識:
最大公約數僅僅有1和它本身的數叫做質數(素數)——這個應該知道吧?-_-b
至今為止,沒有不論什麼人發現素數的分佈規律,也沒有人能用一個公式計算出全部的素數。關於素數的非常多的有趣的性質或者科學家的努力
我不在這裡多說,大家有興趣的話能夠到百度或google搜一下。我在以下列出了一個網址,上面僅僅有個大概。很多其它的知識須要大家一點一點
地動手收集。
http://www.scitom.com.cn/discovery/universe/home01.html
1.高斯推測,n以內的素數個數大約與n/ln(n)相當,或者說,當n非常大時,兩者數量級同樣。這就是著名的素數定理。
2.十七世紀費馬推測,2的2^n次方+1,n=0,1,2…時是素數,這種數叫費馬素數,可惜當n=5時,2^32+1就不是素數,
至今也沒有找到第六個費馬素數。
3.18世紀發現的最大素數是2^31-1,19世紀發現的最大素數是2^127-1,20世紀末人類已知的最大素數是2^859433-1,用十進位制表示,這是一個258715位的數字。
4.孿生素數猜想:差為2的素數有無窮多對。眼下知道的最大的孿生素數是1159142985×2^2304-1和1159142985×2^2304+1。
5.歌德巴赫猜想:大於2的全部偶數均是兩個素數的和,大於5的全部奇數均是三個素數之和。當中第二個猜想是第一個的自然推論,因此歌德巴赫猜想又被稱為1+1問題。我國數學家陳景潤證明了1+2,即全部大於2的偶數都是一個素數和僅僅有兩個素數因數的合數的和。國際上稱為陳氏定理。