Miller-Rabin素數檢測演算法

Gary_818發表於2020-07-19

由於收到某退役學長的鞭策,忽然就想學習一丟數論
來補充一下虎哥基礎數論中沒有出現的東西
本文轉載須聯絡作者,並標明出處


定義

Miller-Rabin素數測試,又稱米勒-拉賓素性檢驗,是一種素數判定法則,利用隨機化演算法判斷一個數是合數還是可能是素數。
卡內基梅隆大學的計算機系教授Gary Lee Miller首先提出了基於廣義黎曼猜想的確定性演算法,由於廣義黎曼猜想並沒有被證明,其後由以色列耶路撒冷希伯來大學的Michael O. Rabin教授作出修改,提出了不依賴於該假設的隨機化演算法。(摘自百度百科)

用處&背景

根據上面的定義可以顯然的看到,這個演算法的主要目的就是進行單個素數的判定
在前期學習當中,我們也學習過單個素數的判定
複雜度為\(O(\sqrt n)\),程式碼如下

bool isPrime(int x) {
    if (x < 2) return false;
    for (int i = int(sqrt(x+0.5)); i >= 2; --i) {
        if (x % i == 0) return false;
    }
    return true;
}

那麼利用Miller-Rabin(簡稱MR)演算法
還有優秀的龜速乘(快速加)以及快速冪
複雜度可以達到\(O(klog_n)\)
MR的複雜度在百科中給出了一大堆\(log\)像這樣:
使用快速傅立葉變換能夠將這個時間推進到\(O(klog_nloglog_nlogloglog_n)=O(klog_n)\)
總之複雜度就是\(O(klog_n)\)

而且正確性也有一定的保障
經過證明(我不會)
每次檢測MR給出的錯誤結果的概率小於等於\(\frac 1 4\)
那麼進行k次檢測的錯誤概率可降低至\(O({\frac 1 4}^k)\)
實際使用效果要比理論值好不少
可以說是相當優秀了

證明

下面來看正確性的證明
需要用到的前置知識:費馬小定理二次探測定理Wilson定理
不太好解釋,沒關係,我們一個一個來看
有個別不懂的演算法可以直接點選右側目錄去看

費馬小定理

性質

若a,p互質,則\(a^{p-1}≡1(mod p)\)

證明

考慮\(1,2,3...(p - 1)\)\(p-1\)個數字,給所有數字同時乘\(a\),得到\(a,2a,3a,...(p - 1)a\)

\[\because a \neq b (mod p), (c, p) = 1 \]

\[\therefore ac \neq bc(mod p) \]

\[\therefore 1*2*3...(p - 1) \equiv a*2a*3a...(p-1)a (mod p) \]

\[\therefore (p-1)! \equiv (p-1)!a^{p-1}(mod p) \]

\[\because ((p-1)!, p) \equiv 1 \]

\[\therefore a^{p-1} \equiv 1(mod p) \]

二次探測定理

性質

如果\(p\)是一個素數,且\(0<x<p\),則方程\(x^2 \equiv 1(mod p)\)的解為\(x = 1, x = p - 1\)

證明

\[\because x^2 - 1 \equiv 0(mod p) \]

\[\therefore (x + 1)(x - 1) \equiv 0(mod p) \]

\[\therefore p|(x -1)(x + 1) \]

\[\because x < p \]

\[\therefore x = 1, x =p -1 \]

Wilson定理

性質

在初等數論中,威爾遜定理給出了判定一個自然數是否為素數的充分必要條件。
即:當且僅當p為素數時:\(( p -1 )! ≡ -1 ( mod p )\)
由於階乘是呈爆炸增長的,其結論對於實際操作意義不大,但藉助計算機的運算能力有廣泛的應用,也可以輔助數學推導。

證明

由二次探測定理,\(1*(p - 1) \equiv 1(modp)\)
在質數p的完全剩餘系當中
一定存在\((a, y) \equiv 1(mod p)\)
根據歐幾里得(\(Euclid\))或者逆元的性質能得到
(實在不想證明了,以前講過,算顯然吧)
那麼在\((2,p-2)\)共計\(p-3\)個元素中
一共能夠找到\(\frac {p - 3} 2\)個這樣的二元組
且都同餘1
現在只剩下了1和p-1兩個元素
顯然兩者相乘與p同餘-1
則命題得證
當且僅當p為素數時:\(( p -1 )! ≡ -1 ( mod p )\)


至此全部的前置知識的證明已經結束,下面就是和程式碼實現有關的部分


實現過程

  • 對於偶數和 0,1,2 可以直接判斷。

  • 設要測試的數為 x,我們取一個較小的質數 a,設 s,t,滿足 \(2^s\cdot t=x-1\)(其中 t 是奇數)。

  • 先求出 \(a^t\),然後不斷地平方並且進行二次探測(進行 s 次)。

  • 最後我們根據費馬小定律,如果最後 \(a^{x-1}\not\equiv 1(mod \:\, x)\),則說明 \(x\) 為合數。

  • 多次取不同的 \(a\) 進行多次 \(Miller \:\ Rabin\) 素數測試,這樣可以使正確性更高

備註

  • 我們可以多選擇幾個 \(a\),如果全部通過,那麼 \(x\) 大概率是質數。

  • \(Miller \:\ Rabin\) 素數測試中,“大概率”意味著概率非常大,基本上可以放心使用。

  • \(a\) 取遍小等於 \(30\) 的所有素數時,可以證明 \(int\) 範圍內的數不會出錯。

  • 程式碼中我用的 \(int\) 型別,不過實際上 \(Miller \:\ Rabin\) 素數測試可以承受更大的範圍。

  • 另外,如果是求一個 \(long long\) 型別的平方,可能會爆掉,因此有時我們要用快速冪,不能直接乘

程式碼實現

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;

int prime[10]={2,3,5,7,11,13,17,19,23,29};
inline int Quick_Multiply(int a, int b, int c){  //快速乘
    int ans = 0;
    while(b){
        if(b & 1) ans = (ans + a) % c;
	    a = (a + a) % c;
	    b >>= 1;
    }
    return ans;
}

inline int Quick_Power(int a, int b, int c){    //快速冪
    int ans = 1;
    while(b){
        if(b & 1) ans = Quick_Multiply(ans, a, c);
        a = Quick_Multiply(a, a, c);
        b >>= 1;
    }
    return ans;
}

inline bool Miller_Rabin(int x){     //判斷素數
    int i, j, k;
    int s = 0, t = x - 1;
    if(x == 2)  return true;   //2是素數 
    if(x < 2 || !(x & 1))  return false;     //如果x是偶數或者是0,1,那它不是素數 
    while(!(t & 1)){  //將x分解成(2^s)*t的樣子
        s++;
        t >>= 1;
    }
    for(i = 0; i < 10 && prime[i] < x; ++i){      //隨便選一個素數進行測試 
        int a = prime[i];
        int b = Quick_Power(a, t, x);      //先算出a^t
        for(j = 1; j <= s; ++j){    //然後進行s次平方 
            k = Quick_Multiply(b, b, x);   //求b的平方 
            if(k == 1 && b != 1 && b != x - 1)     //用二次探測判斷 
            	return false;
            b = k;
        }
        if(b != 1)  return false;   //用費馬小定律判斷 
    }
    return true;   //如果進行多次測試都是對的,那麼x就很有可能是素數 
}

signed main(){
    int x;
    scanf("%lld", &x);
    if(Miller_Rabin(x)) printf("Yes\n");
    else printf("No\n");
    return 0;
}

總結

終於寫完了,一晚上就學習了這一個演算法
\(Markdown\)敲起來真費勁,但是 \(L_{a}\)\({T_ex}\) 是真的好看
感覺這篇部落格寫的很認真,希望能自我提升,也能幫到大家~~

相關文章