1. 程式人生 > 實用技巧 >Miller-Rabin 素數檢驗演算法

Miller-Rabin 素數檢驗演算法

演算法簡介

Miller-Rabin演算法,這是一個很高效的判斷質數的方法,可以在用\(O(logn)\) 的複雜度快速判斷一個數是否是質數。它運用了費馬小定理和二次探測定理這兩個篩質數效率極高的方法。

費馬小定理判質數

\(a^{p - 1}\ ≡\ 1\ mod\ p\)

這個定理在 \(p\) 為質數的時候是成立的,所以我們可以如果要判斷 \(p\) 是否是質數,可以 \(rand\) 幾個 \(a\) 值然後照著這個式子來算,如果算出來不是 \(1\) 那說明 \(p\) 一定不是質數。

但在我們的自然數中,如果照著這個式子算出來的答案為1,也是有可能不是質數的。更有一類合數,它用費馬小定理不管 rand 什麼數都判不掉。這類合數稱為 Carmichael數(

卡邁克爾數),其中一個例子就是561(哇,居然這麼小)。

二次探測定理

因為Carmichael數的存在,使得我們難以高效判斷質數,所以我們還需要加入第二種判斷方法使這種偽演算法更優秀!而二次探測無疑就是為我們量身定製的演算法,因為它要建立在同餘式右邊為1的基礎上(而我們的費馬小定理不正好滿足了要求嗎?)

\(b^2≡1\ mod\ p\)\(p\) 為質數 \(=>\)\(p\) 一定可以被 \(b−1\)\(b+1\) 其中一個整除

這是二次探測定理,原理很簡單,我們將上面的同餘式左右都減1,根據平方差公式可以得出 \((b−1)(b+1)≡\ 0\ mod\ p\)

這其實就代表著等式左邊是模數的倍數,但若模數p是質數,則 \((b−1)\)\((b+1)\) 必定存在一個是 \(p\) 的倍數,所以要麼 \(b−1=p\ (b=1)\) 或者 \(b+1=p\ (b=p−1)\) 如果不滿足則 \(p\) 一定不是質數!然後我們還可以發現若 \(b=1\) 我們又可以進行新一輪二次探測!

根據這個道理,我們可以進行二次探測:因為 \(a^{p−1}≡1\mod\ p\) 如果 \(p−1\) 為偶數的話就可以化成: \(a^{(\frac{p−1}2)^2}≡1\ mod\ p\) 這樣就變成了二次探測的基本式。

typedef long long ll;
typedef unsigned long long ull;
typedef long double lb;
inline ll ksc(ull x, ull y, ll p) {  // O(1)快速乘(防爆long long)
    return (x * y - (ull)((lb)x / p * y) * p + p) % p;
}

inline ll ksm(ll x, ll y, ll p) {  //快速冪
    ll res = 1;
    while (y) {
        if (y & 1) res = ksc(res, x, p);
        x = ksc(x, x, p);
        y >>= 1;
    }
    return res;
}

inline bool mr(ll x, ll p) {
    if (ksm(x, p - 1, p) != 1) return 0;  //費馬小定理
    ll y = p - 1, z;
    while (!(y & 1)) {  //一定要是能化成平方的形式
        y >>= 1;
        z = ksm(x, y, p);                    //計算
        if (z != 1 && z != p - 1) return 0;  //不是質數
        if (z == p - 1) return 1;  //一定要為1,才能繼續二次探測
    }
    return 1;
}

inline bool prime(ll x) {
    if (x < 2) return 0;
    if (x == 2 || x == 3 || x == 5 || x == 7 || x == 43) return 1;
    return mr(2, x) && mr(3, x) && mr(5, x) && mr(7, x) && mr(43, x);
}

這樣子加上二次探測之後,明顯就能高效很多,基本上卡不了,大概要每 \(10^{10}\) 個數才會出現一個判不掉的,這個概率可以說十分微小,可以忽略!