關於素數的判斷,大家最常用的方法估計就是循環判斷到sqrt(n)的方法了:(直接上代碼)
bool isprime(int n) { for(int i=2;i<=sqrt(n);i++) { if(n%i==0) return false; } return true; }
這種素數的判斷方法的確直觀,但這種算法只對較小數據量適用,當數據量較大時,該方法就不再適用於素數的判定了。因此,我們此處引入一種新的算法——素數篩法。
首先介紹一下什么叫素數篩法:
假設所有待判斷的數字的上限是L,聲明一個長度為L+1的布爾數組A[L+1]。用這個數組來表示對應下標的數字是不是素數。起初,將數組所有成員標記為1,然后按照某種方法將其中的非素數都標記為0即可,完成后的數組有這樣的特征:所有素數為下標的成員內存的數字都是1,所有非素數為下標的成員內存的數字都是0。例如 :2 是素數,那么A[2]=1;4不是素數,那么A[4]=0。因此,我們獲取了一個素數表。這樣,判斷一個數是不是素數,直接查找即可。這樣,我們在使用素數的時候就無需再進行素數的判斷,這將大大縮短程序的運行時間,雖然我們需要提前計算素數表,但這相比較於用的時候再進行素數的判定,無疑是一個巨大的進步。下面介紹如何進行素數的標記:
這個標記的方法是這樣的:1不是質數,也不是合數,標記0。第二個數2是質數標記為1,而把2后面所有能被2整除的數都標記為0。2后面第一個沒划去的數是3,把3標記為1,再把3后面所有能被3整除的數都標記為0。3后面第一個沒划去的數是5,把5標記為1,再把5后面所有能被5整除的數都標記為0。這樣一直做下去,就會把不超過N的全部合數都標記為0,留下的就是不超過N的全部質數。因為希臘人是把數寫在塗臘的板上,每要划去一個數,就在上面記以小點,尋求質數的工作完畢后,這許多小點就像一個篩子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼篩法”,簡稱“篩法”。
1 int prime[MAXN];//素數數組 2 bool isprime[MAXN + 10];//is_prime[i]表示i是素數 3 //返回n以內素數的個數 4 int sieve(int n) 5 { 6 int p = 0;//素數個數計數器 7 for (int i = 0; i <= n; i++) 8 is_prime[i] = true; 9 is_prime[0] = is_pri[1] = false;//首先標記0和1不是素數 10 for (int i = 2; i <= n; i++) 11 { 12 if (is_prime[i]) //如果i是素數 13 { 14 prime[++p] = i;//將素數放進素數表 15 for (int j = 2 * i; j <= n; j += i)//所有i的倍數都不是素數 16 is_prime[j] = false; 17 } 18 } 19 return p; 20 }
當然這個方法已經有很大的改進了,但是仍然存在會有重復篩掉某一合數,增加無用計算的現象,例如,刪3的倍數時15標記為0,刪15的倍數時,同樣再一次將15標記為0。這還不夠精簡,因此有人將這個方法進行了改進:
首先初始A[1]=0,A[2]=1;
其次,將所有偶數下標的成員全部置為0,
接下來求出 t=√A
接下來從i=3開始到i=t開始當A[i]=1時進行下列操作
{
從 j=i*i 開始,到 j=L 結束,每次 j 加 2*i 讓A[j]=0
}
改進原理如下:
將1和2進行初始化很容易理解
接着是將所有的偶數標記為0,這也很好理解,因為除了2之外所有的偶數都不是素數,這樣一來,數據處理量直接縮小一半,爽!!!!
接下來就是問題的關鍵了,也是比較難理解的一部分,為什么再循環判斷的時候到√A 就結束了哪?我們是這樣解釋的,因為在內部循環中我們是讓 j 從i * i 開始循環的,如果 i > √A ,呢么我們一定可以保證 i * i 是大於 L 的,所有就沒有必要再繼續循環了。內循環的處理原理是這樣的:實際上我們處理的是 i 的倍數是不是素數的問題.
為什么 i 從3 開始 到 t 便可以結束呢?原因是循環之內(黃色字體)的 j 每次是以 i * i 為初始值進行判斷的, 如果 i > t ,那么 i * i 一定 大於 L,所以 就沒必要進行 t 之后的循環了。只判斷 A[ i ]= 1(即 A[ i ]是奇數)的情況,原因是 循環之內的處理,實際上處理的是 i 的倍數是不是素數的問題,大家都清楚,不僅 2 的所有倍數是偶數,所有偶數的倍數都是偶數。
在解釋循環之內(黃色字體)的內容,有人很好奇,為什么不用考慮 i *3 到 i *( i-1)之間的數呢,那么假設 有一個數 p 介於 3 和( i - 1)之間, 顯然 ,如果 i * p 是小於 L 范圍之內的數, 在 i = p
的時候,就應該判斷過這個數了。
j = i * i 是非素數,這個就很明顯了。
至於為什么 j 每次的增量是 2 * i ,而不是 i 呢?因為奇數個奇數相加一定是奇數,偶數個奇數相加一定是偶數。首先我們已經在判斷時保證 i 是 奇數,那么 i 個i 相加就可以表示為 i * i,如果增量是 i 且 i * i
是奇數的話,i * i +i 必定是偶數, i*i+2i 才是奇數,也就說增量是 i 的時候,每兩次循環中,有一次 就判斷偶數(偶數之前已經被排除過了),這樣豈不是違背了要提高效率的初衷?因此,在當前循環中需
要處理的只是 奇數且是非素數的情況。j =i * i+2i=i *( i + 2)顯然也是非素數,以此類推 j = j +2*i 是非素數,直至該數超過上限結束本次循環。
這樣就保證萬無一失且不重復的排除所有情況啦。
好了直接上代碼
1 int prime[MAXN];//素數數組 2 bool isprime[MAXN + 10];//is_pri[i]表示i是素數 3 //返回n以內素數的個數 4 int sieve(int n) 5 { 6 int p = 0;//素數個數計數器 7 for (int i = 0; i <= n; i++) 8 is_prime[i] = true; 9 is_prime[0] = is_prime[1] = false;//首先標記0和1不是素數 10 is_prime[2] = true;//標記2是素數 11 12 for (int i = 2; i <= sqrt(n); i++) 13 { 14 if (is_prime[i]) //如果i是素數 15 { 16 prime[++p] = i;//將素數放進素數表 17 for (int j = i * i; j <= n; j += 2 * i)//所有i的倍數都不是素數 18 is_prime[j] = false; 19 } 20 } 21 return p; 22 }