質數的兩種篩法


目錄

目錄地址

上一篇

下一篇

內含部分高數內容,請不想了解證明的小伙伴直接參考小標題后面的時間復雜度

質數的朴素篩法:\(O({n\sqrt n\over \log n})\)

根據定義,我們不難得出,如果要知道 \(1\)~\(n\) 范圍內的所有質數,我們只需要從 \(2\)\(n\) 開始枚舉,再判斷是否是質數即可:

bool isprime[MAXN];
for(int i=2;i<=n;i++){
    isprime[i]=1;
    for(int j=2;j*j<=i;j++){
        if(i%j==0){
            isprime[i]=0;
            break;
        }
    }
}

當枚舉到的數為 \(n\) 的時候,內層的復雜度是 \(O(\sqrt n)\) 的,而外層 \(O(n)\) 枚舉

因此,很多人覺得是 \(O(n\sqrt n)\)

其實,本人對此持懷疑態度

首先:每個合數都是被自己的最小質因子篩到,而每個質數 \(p\) 花費的時間是 \(O(\sqrt p)\)

質數的很顯然,對於合數的,我們用反證法:如果這個數 \(m\) 的最小質因數為 \(fc\) ,它被判定為合數時 \(i=k\)

因此, \(m\) 應該在 \(k\) 之前都不能退出循環

而如果 \(fc\)\(k\) 的因數,\(fc<k\)(因為 \(k\) 為合數);如果不為的話, \(k\) 的最小質因數假設為 \(fc'\)\(fc<fc'<k\)

因此, \(m\)\(fc\) 時就一定退出了

綜上,復雜度其實並沒有達到 \(O(n\sqrt n)\) ,其實復雜度是更小的

經本人 不嚴謹證明 復雜度大概為 \(O({n^{3\over 2}\over \log n})\)

優化 \(o({n^{3\over 2}\over \log n})\)

我們考慮每個合數,一定是被它的最小質因數篩到。而一個數 \(n\) 的最小質因數 \(fc\) ,一定有 \(fc\in Prime,fc\leq n\)

所以,我們把之前篩到的所有質數存起來,篩到 \(n\) 時,依次枚舉不大於 \(\sqrt n\) 的質數判斷是不是這個數的因數就行了

bool isprime[MAXN];
int prime[MAXN],cntprime=0;
for(int i=3;i<=n;i++){
    isprime[i]=1;
    for(int j=1;prime[j]*prime[j]<=i&&j<=cntprime;j++){
        if(i%prime[j]==0){
            isprime[i]=0;
            break;
        }
    }
    if(isprime[i]==1){
        prime[++cntprime]=i;
    }
}

枚舉質數的速度比優化前更快。優化前枚舉到第 \(t\) 個質數 \(p_t\) 的開銷是 \(O(p_t)\) ,優化后是 \(O(t)\)


埃氏篩 \(O(n\log\log n)\)

埃拉托斯特尼(Eratosthenes)篩法,簡稱埃氏篩,由希臘數學家埃拉托斯特尼所提出的一種簡單檢定素數的算法。

顯然,對於某個質數 \(p\) ,它的倍數(除了它本身)一定不是質數

因此,我們把每枚舉一個質數 \(p\) ,就把它的這些倍數全部打上不是質數的標記

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i+i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

有人認為這個復雜度的計算是 \(\displaystyle T(n)=\lfloor{n\over 1}\rfloor+\lfloor{n\over 2}\rfloor+\lfloor{n\over 3}\rfloor+\cdots+\lfloor{n\over n}\rfloor\approx{n\over 1}+{n\over 2}+{n\over 3}+\cdots+{n\over n}=n\sum_{i=1}^n{1\over i}=n(\ln n+\gamma)\) ,得出復雜度 \(O(n\log n)\) 的結論

其實這個估計偏大了:

同樣身經本人 不嚴謹證明 ,復雜度應為 \(O(n\log\log n)\)

優化 \(O(n\log\log n)\)

我們可以發現,每個數字實際上被它的所有質因數都篩了一遍:

比如 \(6=2\times 3\),就被 \(2\)\(3\) 各篩了一次,這就導致了為什么復雜度是 \(O(n\log\log n)\) 而不是 \(O(n)\)

我們考慮到,對任意質數 \(p\) ,以它為最小質因數的數字最小為 \(p^2\)

所以我們換一個枚舉下界:從 \(p^2\) 開始枚舉即可

bool isntprime[MAXN]={0};
for(int i=2;i<=n;i++){
    if(isntprime[i]==1) continue;
    for(int j=i*i;i<=n;j+=i){
        isntprime[j]=1;
    }
}

我們重新計算一下時間復雜度,可以發現,時間復雜度不變,但常數更小


線性篩 \(O(n)\)

歐拉(Euler)篩法,簡稱歐式篩,或因為其線性復雜度被稱呼為線性篩。由瑞士數學家歐拉提出

它在埃氏篩的基礎上,用一個方法,限定了每個數只被其最小質因數 \(fc\) 篩到一次,從而保證時間復雜度為 \(O(n)\)

思想比較巧妙:

對於當前數字 \(n\) ,假設它的最小質因數為 \(fc\)

對於已經篩出的質數,存在表 prime 中

那么,我們從質數表中,枚舉最小質因數不大於 \(fc\) 的質數 \(p\)

我們就能保證: \(p\times n\) 的最小質因數一定為 \(p\)

那么,事先沒被標記最小質因數的數字就一定是質數,且最小質因數為它本身

代碼實現如下:

int fc[MAXN]={0},prime[MAXN],cntprime=0;
for(int i=2;i<=n;i++){
    if(fc[i]==0){
        fc[i]=i;
        prime[++cntprime]=1;
    }
    for(int j=1;j<=cntprime&&prime[j]<=fc[i]&&prime[j]*i<=n;j++){
        fc[i*prime[j]]=prime[j];
    }
}

這個代碼推薦背住,對后期的簡單積性函數也可以使用該篩法,實現線性時間內求出

STL-版

vector<int> Prime;
int fc[MAXN];
for(int i=2;i<=n;i++){
    if(fc[i]==0){
        fc[i]=i;
        Prime.push_back(i);
    }
    for(auto p : Prime)
        if(p>fc[i]||p*i>n) break;
        else fc[p*i]=p;
}


免責聲明!

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



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