之前在解釋求素數的一道習題時,提過一個方法,叫素數篩法。下面就對這種方法的過程進行詳細的解讀。
之前提到
假設所有待判斷的數字的上限是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的全部質數。因為希臘人是把數寫在塗臘的板上,每要划去一個數,就在上面記以小點,尋求質數的工作完畢后,這許多小點就像一個篩子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼篩法”,簡稱“篩法”。
當然這個方法會有重復篩掉某一合數的現象,例如,刪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⃣️首先,將1 和 2 進行初始,1 為非素數,A[1]= 0, 2 為 素數,A[2]= 1。
2⃣️所有的偶數都是2的倍數,也就意味着,所有的偶數都能被2整除,因此所有偶數不是素數。那么將所有偶數下標的成員賦值為0是合理的。這就將素數的范圍縮小為奇數。
3⃣️為什么 i 從3 開始 到 t 便可以結束呢?原因是循環之內(黃色字體)的 j 每次是以 i * i 為初始值進行判斷的, 如果 i > t ,那么 i * i 一定 大於 L,所以 就沒必要進行 t 之后的循環了。只判斷 A[ i ]= 1(即 A[ i ]是奇數)的情況,原因是 循環之內的處理,實際上處理的是 i 的倍數是不是素數的問題,大家都清楚,不僅 2 的所有倍數是偶數,所有偶數的倍數都是偶數。
4⃣️現在解釋循環之內(黃色字體)的內容,有人很好奇,為什么不用考慮 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 是非素數,直至該數超過上限結束本次循環。
這樣就保證萬無一失且不重復的排除所有情況啦。
初看這個方法,覺得好奇怪,仔細品味,回味無窮。
我的微信公眾號為“昵昵的寶典”,歡迎大家關注了解最新的內容。