標籤:
素數的驗證,可能會被作為所謂“迴圈練習”的題目。因為其演算法實在太簡單(不知道直接暴力迴圈能不能算一種演算法)。經典的方法就是試除,用迴圈變數i從2開始到n-1,如果有模數為0的,就直接return false。到最後,還沒有模出0,就return true。這個演算法也可以最佳化n-1為sqrt(n)。原理是:設x為大於sqrt(n)的數,n=xy,則y一定小於sqrt(n),所以只要驗證到sqrt(n),就能驗證到y,相當於驗證到了x。
以上這種暴力演算法,如果有N個數要驗證,則時間複雜度為O(sqrt(N)*N),面對類似於N=100000000的資料明顯力不從心。所以,我們可以使用篩選法:依次剔除2、3、5、7等素數的倍數,最後就能得出一張素數表。建表後,查詢素數只要O(1)的時間複雜度。但是對於以上數量級的N,仍然不能很快的求出一張素數表,消耗時間高達2.1s。所以,我們還要加以最佳化。
眾所周知,除去2以外,所有的偶數均為合數。所以,素數表內可以除去偶數。即prime[0]代表3是否為素數,prime[1]代表5……而且,篩數也不必篩到N,只要篩到sqrt(N)即可。設key1(i)=i*2+3;key2(i)=(i-3)/2,這分別對應素數表內第i個位置表示的奇數與奇數i在素數表內的儲存位置。原來篩的時候,i*(2,3,4…)簡化為了i*(3,5,7…)。頭一次篩掉的數為key2(key1(i*i)),簡化後為(i*i)*8+3,之後每次累加的數為key2(key1(i+2))-key2(key1(i)),簡化後為i*2+3,這樣推導完成以後,程式就很容易寫了。下面給出原始碼:
#include <cstring>#include <cmath>#include <cstdio>using namespace std;const int N=100000001;const int N1=(N-3)/2;bool prime[N1+1];int tmp;int main(void){ memset(prime,true,sizeof(prime)); for (int i=0;i<(int(sqrt(N))-3)>>1;++i){ if (prime[i]){ tmp=((i*i)<<3)+3; while (tmp<N1){ prime[tmp]=false; tmp+=(i<<1)+3; } } } printf("%d\t",2); for (int i=0;i<N1;++i) if (prime[i]) printf("%d\t",i*2+3); return 0;}
附上各種方法的時間與記憶體佔用統計表(測試環境:MinGw4.9.2,Intel Xeon E3 1230V2,去除IO輸出時間):
|
時間 |
記憶體 |
暴力試除法 |
N/A(太長了) |
1Mb |
樸素篩選法 |
2.1s |
98Mb |
最佳化後的篩選法 |
1.0s |
50Mb |
素數驗證演算法——直面大資料