一般篩法求素數
標籤: PAT
下面是一個模板代碼,在PAT考試中能夠流暢默寫會大大加速我們的編程速度,一般篩法雖然在速度上不如線性篩法,但是在PAT考試中,這一點性能可以忽略不計了.另外需要特別注意的是:
PAT打表解法有可能無法提交!
PAT打表解法有可能無法提交!
PAT打表解法有可能無法提交!
重要的事情說三遍!,一定要記住
在這樣的情況下,熟練掌握篩法就是至關重要的了.
下面是沒有優化的代碼,
bool prime [MAX];
memset(prime,true,MAX);
vector<long> v;
for(int i = 2; i < MAX; i ++){
if(prime[i] == true){
v.push_back(i);
for(int j = i ; j < MAX ; j += i){
prime[j] = false;
}
}
}
有必要作幾點說明:
- prime最好使用bool數組,bool雖然邏輯上是佔用一個字節.但是實際上,在定義成一個bool數組後,編譯器有可能會對這個數組做一些優化,使用一個字節的二進制位來代表true或者false.所以在一些數字比較大,對內存的限制比較緊張的情況下,使用bool數組可以有效的節省內存.
- memset函數的使用,爲了避免因爲平臺不一樣導致奇怪的問題,這裏統一引入
memory.h
頭文件. - 爲了後面輸出等其他操作的方便,存放素數的容器這裏統一使用vector.
下面是一些可以優化的地方
- 內層循環中,j 從 i開始篩後面的數字,這樣其實是完全不必要的.可以直接讓j 從 i的平方開始往後篩.這樣做的理由是i 到 i平方之間會路過的數字肯定已經被比i小的數字篩過了.例如:質數5如果按照常規方法,要經過
5
,10
,15
,20
,25
.將這些數字拆分就會發現依次是1 * 5
,2 * 5
,3 * 5
,4 * 5
,5 * 5
,而1
,2
,3
,4
與5
的乘積在之前肯定已經被篩過.所以可以直接讓j = i * i開始內層循環.- 上面這種優化仍然是存在問題的.這會讓j的定義必須變得特別大.因爲當i = 10000的時候,j就必須要達到100000000纔可以容納,這回導致沒有某種類型可以容納它.所以我們讓i的初始值定義爲
sqrt(double(MAX) + 0.5)
,對MAX四捨五入後取根號操作,就可以讓篩法的範圍正好覆蓋MAX的範圍.
即使這樣優化仍然有需要注意的地方
就是將素數放入vector的操作的位置.因爲檢查prime[i]是否爲true的操作現在只進行到根號MAX
就停止了.所以原來地方的對V的操作無法訪問所有爲true的prime元素.所以在執行完篩法之後,我們需要再掃描一遍prime數組,將prime爲true的元素單獨放入.
典型例題
- PAT 1013 數素數:利用素數個數定理x/ln(x)估算出達到目標的素數範圍,使用篩法曬出素數表.即可求解,注意控制格式.
- PAT 1027 素數對擦想:篩出固定範圍的表後,遍歷減去相鄰的即可求得個數.
下面是最終代碼
bool prime [MAX];
memset(prime,true,MAX);
vector<long> v;
int m = sqrt(double(MAX) + 0.5);
for(int i = 2; i < m; i ++){
if(prime[i] == true){
for(int j = i * i ; j < MAX ; j += i){
prime[j] = false;
}
}
}
for(long i = 2 ; i < MAX;i ++){
if(prime[i])
v.push_back(i);
}