素數專題——素數篩法


關於素數的判斷,大家最常用的方法估計就是循環判斷到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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM