由於收到某退役學長的鞭策,忽然就想學習一丟數論
來補充一下虎哥基礎數論中沒有出現的東西
本文轉載須聯絡作者,並標明出處
定義
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\)
二次探測定理
性質
如果\(p\)是一個素數,且\(0<x<p\),則方程\(x^2 \equiv 1(mod p)\)的解為\(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}\) 是真的好看
感覺這篇部落格寫的很認真,希望能自我提升,也能幫到大家~~