幾種判斷質數的算法


有一個正整數 \(n\) ,試判斷 \(n\) 是不是質數。

經典模板了屬於是
主要有質數篩、枚舉因子、Miller Rabin 算法三種做法

1. 質數篩

分為埃氏篩和歐拉篩(線性篩)兩種

埃氏篩應該是判斷質數的最基礎方法了
\(2\) 開始從小到大依次枚舉整數
如果沒被篩過就說明是質數,同時應將其所有倍數(除本身外)篩去

    v[1]=1; // 值為 1 表示非質數,0 表示是質數
    for (int i=2; i<=n; ++i) if (!v[i])
        for (int j=(i<<1); j<=n; j+=i) v[j]=1;

時間復雜度 \(O(n\log \log n)\)

歐拉篩同樣是篩倍數,但用了一些技巧將時間復雜度降至了 \(O(n)\)

    v[1]=1;
    for (int i=2; i<=n; ++i) {
        if (!v[i]) p[++cnt]=i;
        for (int j=1; j<=cnt&&p[j]*i<=n; ++j) {
            v[p[j]*i]=1;
            if (i%p[j]==0) break;
        }
    }

關鍵在於每個數恰好被其最小質因子篩一次
看懂這個結論之后 正確性和時間復雜度就都很顯然了

2. 枚舉因子

質數的定義:只有兩個正因數(\(1\) 和本身)的自然數是質數。
所以只需判斷 \(2\sim n-1\) 中有沒有 \(n\) 的因子
\(x\) 的因子是成對出現的,每一對因子中較小者必然不超過 \(\sqrt n\) ,因此從 \(2\) 枚舉到 \(\sqrt n\) 即可

int isp(int x) { //判斷質數,是則返回 1 ,否則返回 0
    if (x==1) return 0; // 1 既不是質數也不是合數
    for (int i=2; i*i<=x; ++i)
        if (x%i==0) return 0;
    return 1;
}

時間復雜度為 \(O(\sqrt n)\)

細想一下發現僅枚舉質因子即可保證正確性
所以有一種優化思路是盡量避免枚舉合數

考慮如下結論:
大於 \(3\) 的質數必然和 \(6\) 的倍數相鄰。

\(k\) 為正整數,則 \(6k-2,6k,6k+2,6k+3\) 顯然都為合數
因此只需枚舉形如 \(6k\pm 1(k\in\mathbb N^*)\) 的數即可

int isp(int x) {
    if (x<5) return (x==2||x==3); // 注意 2,3 需要特判
    if (x%6!=1&&x%6!=5) return 0;
    for (int i=5; i*i<=x; i+=6)
        if (x%i==0||x%(i+2)==0) return 0;
    return 1;
}

時間復雜度依然為 \(O(\sqrt n)\) ,但實際運行要比優化前快幾倍,而且代碼也不長,日常做題推薦使用

3. Miller-Rabin 素性檢測

這篇文章講得非常詳細了:link

時間復雜度 \(O(k\log n)\) ,其中 \(k\) 為測試次數

OI 中可以選擇固定的幾個常數做測試,在保證准確的前提下:
\(2^{32}\) 以內選擇 \(2,7,61\) 三個數即可
\(10^{16}\) 以內選擇 \(2,3,7,13,61,24251\) 六個數即可
數據范圍更大時可以換成隨機數,具體實現可參考上面提到的文章

4. 總結

算法
時間復雜度
空間復雜度
線性篩 \(O(n)-O(1)\) \(O(n)\)
埃氏篩 \(O(n\log \log n)-O(1)\) \(O(n)\)
暴力 \(O(\sqrt n)\) \(O(1)\)
Miller-Rabin \(O(k\log n)\) \(O(k)\)

\(O(n)-O(1)\) 表示預處理 \(O(n)\) ,回答單次詢問 \(O(1)\) (埃氏篩時間復雜度同理)

如果數據范圍較小,用暴力隨便搞搞就行了
如果詢問次數很多且 \(n\) 不是很大,應使用篩法(推薦線性篩)
Miller-Rabin 代碼量大,寫起來比較耗時,盡量少用
考場上很少會出現必須用 Miller-Rabin 的情況,所以這個算法不用練得太熟 差不多得了


免責聲明!

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



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