所謂孿生素數,就是相差爲2的素數對,例如3和5,11和13。若是僅僅是100之內的孿生素數,相信大部分人只用數就能數出來,畢竟100之內只有25個素數。可是若是是1000之內呢?100000之內呢?若是像題目中說的同樣,一億之內呢?ios
硬着頭皮數顯然不行了,要解決這個問題,咱們要依賴於編程。算法
要求孿生素數的對數,首先要找到孿生素數,要找到孿生素數,首先要找到素數。C++中有許多找素數的方法,好比基礎的試除法,其代碼以下:編程
bool prime(int n) { if(n<2) return false; for(int i=2;i*i<=n;i++) { if(n%i==0) return false; } return true; }
這段代碼比較基礎,也很容易理解。優化
完整程序以下:spa
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int n,cnt; //cnt記錄孿生素數對數 bool prime(int n) //試除法篩素數 { if(n<2) return false; for(int i=2;i*i<=n;i++) { if(n%i==0) return false; } return true; } int main() { scanf("%d",&n); for(int i=2;i<=n;i++) { if(prime(i)&&prime(i+2)) //判斷是否知足孿生素數定義 { cnt++; } } printf("%d\n",cnt);return 0; }
一切都很順利的進行了,咱們不由暗想:翻譯
然而,當輸入「100000000」時,奇怪的事情發生了,答案久久沒有出如今小黑板上,只有光標在閃動着寂寞的白光,宛若孤獨而無人陪伴的我。3d
這使人疑惑,因而我關閉了窗口,從新運行,並輸入了較小的數。答案几乎是在敲回車後的一剎那出如今小黑板上。code
這說明程序沒有問題,輸入「100000000」答案遲遲不出現,只有一個可能——程序在運算結果。blog
既然如此,那咱們能作的就只有等待。博客
終於,在不知多久以後,小黑板上終於出現了咱們所但願看到的東西——440312。
雖然獲得告終果,但比起這個,咱們更想知道它到底算了多久,因而我在程序中加入了從百度抄來的計時程序,以下:
#include<iostream> #include<cstdio> #include<algorithm> #include<time.h> //頭文件 using namespace std; clock_t start,finish; //定義始終 double duration; //定義時間 int n,cnt; bool prime(int n) { if(n<2) return false; for(int i=2;i*i<=n;i++) { if(n%i==0) return false; } return true; } int main() { scanf("%d",&n); start=clock(); //在程序開始運行時開始計時,注意,若將這句話加在輸入以前,會把輸入數據的時間記入,影響結果 for(int i=2;i<=n-2;i++) { if(prime(i)&&prime(i+2)) { cnt++; } } finish=clock(); //結束計時 duration=(double)(finish - start)/CLOCKS_PER_SEC; //計算時間 printf( "%f seconds\n",duration); //輸出時間 printf("%d\n",cnt);return 0; }
當輸入「10000」,運算時間爲0.002000s,輸入「1000000」,運算時間爲0.310000s,兩次運算時間並無差不少。
但輸入「100000000」,在又一次漫長的等待後,小黑板上出現了驚人的149.797000s,是計算1000000之內的孿生素數對數所用時間的約483倍。
這可怕的數字令咱們感到恐懼,如果數據範圍再大一些,試除法豈不是要算一年!
看來數據太大,用試除法求解是行不通了,咱們須要的是效率更高的算法。
提到高效算法,聰明的你必定能想到Eratosthenes篩選法,翻譯成人話就是埃氏篩。
埃氏篩的基本思想:素數的倍數必定不是素數。先假設全部數都是素數,從小到大枚舉每個素數x,把x的倍數都標記爲非素數。當從小到大掃描到一個數x時,若它還沒有被標記,則它不能被2~x-1之間的任何數整除,該數就是素數。(對整數1特殊處理)
埃氏篩代碼以下:
void primes(int n) { memset(v,0,sizeof(v)); //合數標記 for(int i=2;i<=n;i++) { if(v[i]) continue; cout<<i<<endl; //i是素數 for(int j=i;j<=n/i;j++) { v[i*j]=1; } }
}
埃氏篩的時間複雜度是O(nloglogn),效率很是接近線性,是一種經常使用的素數篩法。然而,埃氏篩會對合數進行重複標記,即便是優化以後,其根本緣由是算法不能惟一肯定產生合數的方式。據此,咱們在生成一個須要標記的合數時,每次只向現有的數乘上一個質因子,而且讓它是這個合數的最小質因子。這至關於讓合數的質因子從大到小累積。具體來講,咱們採用以下的篩法:
int v[maxn],prime[maxn]; void primes (int n) //用線性篩找素數 { memset(v,0,sizeof(v)); //最小質因子 m=0; //素數數量 for(int i=2;i<=n;i++) { if(!v[i]) //i是質數 { v[i]=i; prime[++m]=i; } //給當前的數i乘上一個質因子 for(int j=1;j<=m;j++) { //i有比prime[j]更小的質因子,或者超出n的範圍 if(prime[j]>v[i]||prime[j]>n/i) break; //prime[j]是合數i*prime[j]的最小質因子 v[i*prime[j]]=prime[j]; } } }
這即是線性篩。每一個合數只會被它的最小質因子篩一次,時間複雜度爲O(N)。
篩法介紹完了,求孿生素數對的程序也就不難寫了,只須要判斷與一個素數相差2的數是否爲素數便可。這個任務交給讀者自行完成。
下面我簡要說一下測評結果。
這是使用線性篩求對數並輸出孿生素數對的運行結果,總共跑了173.793000s,若僅僅輸出對數,只須要1.410000s,比試除法快了近107倍。
埃氏篩也不敢示弱,跑出了178.203000s和3.105000s的不俗成績。
(順便一提,用試除法求孿生素數對,若是要輸出孿生素數是什麼,它須要跑約328s)
相比於基礎的試除法,這兩種算法的效率都高得沒話說。
正所謂,永恆與剎那間,只隔着個人算法。既然有算法能幾秒內解決問題,那爲何不用呢?多節約出幾分鐘,不就能多聽幾首銀臨的歌了嗎。
因此,下次有個要求素數的題,嘗試用埃氏篩和線性篩吧。
若是這篇博客對你有幫助,就請留下一個大拇指,最好還能點個推薦,求求您了,俺求求您了!