Miller_Rabin:读作米勒罗宾。知道这个名字是学习这个算法的唯一难点。
怎么判断一个数p是否是素数???
最容易想到的就是去判断从2到的整数中,有没有可以整除p的数。(因为素数的定义就是从只有1和它本身是它自己的因数啊)。
但是很明显,这种方法需要的时间是的,所以在遇到一些比较大的数字的时候,这种方法就很麻瓜了。
1.费马小定理:当a与p互质的时候,且p是素数的时候,
,例如:a是4,p是7.
那么,a的 6(7-1)次方,也就是4096,再对7取余,结果就是1.
那么反过来,如果当a的p-1次方再对p取余的结果是1,那么是否就说明p是素数呢???很可惜,并不是。还存在着一些数(伪素数)也可以有这种性质,但是这确实可以成为判断素数的一种方法,只不过不准确而已。接下来就可以看到它的用处2.
2.二次探测定理:当p是素数的时候,x是小于p的正整数,并且
,那么x的值就
只能是1或者p-1.这是一个必要条件!!!!就是说p是素数的时候,x只能是1或者p-1.(我没有说反过来也成立,比如8^2%9=64%9=1),是不是把费马小定理和二次探测定理结合就可以完全判断了呢??很可惜,也不是。
简单证明:把1移动到左边,x^2-1=0 mod p.
即 (x+1)*(x-1)=0 mod p.很明显,x=-1,或者1,(注:这里的1,和-1是在mod p意义下的1和-1,x=-1,其实x=p-1)。
怎么利用这个性质判断p是否为素数??
令a=x^2,那么可以知道,要满足上述式子,a开方的值一定会是p-1,或者是1。
结合上面的费马小定理,也就给了一个判定的思路,我们随机的选取小于p的值b,去计算b^(p-1) mod p,如果p是素数,怎么结果必然是1.那的值就必然会是1,或者p-1了。如果
的值是1,那不就是又去判断
的值是不是1或者p-1. 直到没办法可以开平方为止。实际代码过程有一些出入。代码实现是去找最终不能开方的次方,再不断的平方的。没说清??不慌,看了代码就懂了。
以上就是大概的判断过程,上面也说了,这种判断方法并不可靠,根据那些厉害的人推出的错误期望:每一次有25%的几率是错的,具体证明我不证了(好吧,我证明不来,也没看到一个正儿八经的证明),也就是通过了所有的测试,但是实际上p是一个合数。
25%的错误率很明显太高了,那就多选几次来测试不就可以提高准确率了,比如,选10次判断错误的概率就是:(0.25)^10,也就是百万分之一了,选15次错误的几率就是十亿分之一了,一生中被雷劈的概率也就是1.2万分之一,破记录的强力球头奖也就2.92亿分之一,被陨石砸中的记录是7亿分之一,所以,这算法还是很可靠的。
时间复杂度???把代码看懂了就知道了。(其实写这个是为了后面的泼辣肉算法)。
最后:代码(有些细节地方需要自己理解,只写这么多了,再写就让人烦了)。
#include<bits/stdc++.h>
using namespace std;
long long int mod;
long long int quick_pow(long long int n,long long int base)
{
long long int ans=1;
while(n)
{
if(n&1)
ans*=base,ans%=mod;
n>>=1;
base*=base;
base%=mod;
}
return ans;
}
bool Miller_Rabin(long long int val,long long int a)
{
if(val==a)
return true;
if(val%a==0||val<2)
return false;
long long int u=val-1;
int k=0;
while(!(u&1))
{
u>>=1;
++k;
}
long long int temp=quick_pow(u,a)%mod;
if(temp==1||temp==val-1)
return true;
for(int i=1;i<=k;++i)
{
temp=temp*temp%mod;
if(temp==val-1)
return true;
}
return false;
}
int main()
{
long long int n;
long long int a;
cin>>n;
mod=n;
srand(time(0));
bool flag=true;
for(int i=0;i<15&&n!=2;++i)
{
a=rand()%(n-2)+2;
if(!Miller_Rabin(n,a))
{
flag=false;
break;
}
}
if(flag)
cout<<"n is prime"<<endl;
else
cout<<"n not's prime"<<endl;
return 0;
}