指在乙個大於1的自然數中,除了1和此整數自身外,不能被其他自然數(不包括0)整除的數。
根據素數的定義,我們很容易想到的一種判斷方法就是:對於大於1的正整數n,從2開始到n-1依次判斷是不是n的因子,如果存在乙個數是n的因子,那麼n就不是素數,否則就是素數。很顯然這樣的演算法,時間效率為o(n)
通過分析,我們發現其實只要從2開始到[√n]依次判斷是不是n的因子就可以了。這樣的話演算法的效率提高到了o(√n)。
為了提高程式執行的效率,我們想到了一種更加高明的方法,那就是構造乙個hash表,姑且稱之為素數表is_prime(其實就是乙個陣列),is_prime[i]是乙個boolean型別的值,當is_prime[i] == true時,說明i是乙個素數,當is_prime[i] == false時,說明i不是乙個素數。當構造完這張hash表之後,每次我們要判斷整數n是否是素數,只需要讀取is_prime[n]的值就可以。很顯然,這樣處理之後,就達到了「一勞永逸」的效果。
那麼我們如何構造上面提到的那個is_prime呢?當然,我們很容易想到用3、中提到的方法。這樣的話,當我們構造乙個大小為n的素數表,演算法時間複雜度為o(n√n)。其實,我們還有更加高效的方法來構造素數表is_prime,那就是使用篩選法構造素數表。
所謂「篩法構造素數表」就是通過「篩選法」構造乙個hash表is_prime。演算法處理完之後,is_prime[i]的值是乙個boolean型別的值,當is_prime[i]==false的時候,表示i不是乙個素數,相反,當is_prime[i] == true的時候,表示i是乙個素數。
對於不超過n的每個非負整數p,刪除2p,3p,4p,……,當處理完所有數之後,沒有被刪除的的就是素數。
構造素數表的**片段如下:
//init
for(int i = 2; i <= n; i ++) is_prime[i] = true;
//construct
for(int i = 2; i <= n; i ++)
for(int j = i * 2; j <= n; j += i)
is_prime[j] = false;
我們來分析一下3、中**段的時間複雜度,外層迴圈的次數是n-1,給定外層迴圈變數i,內層迴圈的次數是[n/i]-1 < n/i。這樣迴圈的總次數小於n/2 + n/3 + ……+n/n = o(nlogn)。這個結論**於尤拉在2023年得到的結果:1 + 1/2 + 1/3 + …… + 1/n = ln(n + 1) + r,其中尤拉常數r=0.577218。很顯然o(nlogn)比o(n√n)的複雜度要底。
**有兩處可以改進:
1、通過分析,我們發現其實「對於不超過n的每乙個非負整數p」中,p可以限定位素數,因為p若為合數,它肯定是比它小的幾個素數的積。所以p的整數倍已經被篩選掉了。
2、其次,內層迴圈也不必從i*2開始,它已經在i=2時被篩選掉了。
改進後的**片段如下:
//init
for(int i = 2; i <= n; i ++) is_prime[i] = true;
//construct
for(int i = 2; i <= n; i ++)if(is_prime[i])
for(int j = i * i; j <= n; j += i)
is_prime[j] = false;
從「篩選法構造素數表」演算法中,我們主要可以學到兩點:
1、要靈活運用「等到用的時候再處理」和「先處理後備用」這兩種策略。
2、學會反向思考,其實篩選法就是排除法,類似於數學中的反證法。
素數篩選法
篩選素數法 搞acm的都知道,素數是數論中必不可少的知識,也是必須要掌握的,關於素數的篩選有好幾種方法,下面一一道來,寫的不好還請提出。第一種是最常規的做法 int main if j sqrt i cout 這種方法肯定是比第一種快的,至於快多少大家可以比較一下,注意到裡面的for迴圈是到sqrt...
素數篩選法
素數篩選法差不多是打標,用前面確定的質數篩選掉後面的合數,然後遍歷下來所有的合數都被篩選掉了,剩下的都是素數。int vis maxn for int i 2 i n i for int j i 2 j n j i vis j 1 這是沒有優化的素數篩選法,也已經很快了,時間複雜度是n log n。...
素數篩選法
素數,是指因子只包含1和其本身的數,那麼,我們怎麼判斷素數呢?以下 均基於打表 1 1e6 的基礎上完成 素數的定義就是乙個數的因子只包含1和其本身,那麼我們直接就按照定義寫 include include define maxn 1000000 10 int pri maxn int isprim...