米勒拉宾算法——素性测试

背景

对于一个数n,如果想要判断它是否为素数,常规的方法为试除法。即,让n依次除以2 n \sqrt{n} n 以内的整数。如果有出现除尽的情况,则为合数。
该方法的时间复杂度为 O ( n ) O(\sqrt{n}) O(n ),在面对n为长整型的时候有可能超出时间要求。
因为时间复杂度的优势,对于大整数的素性判定,要使用米勒拉宾算法。

伪素数判定

在此之前介绍一种伪素数判定方法——小费马定理。伪素数判定的意思是,对于大部分的数,可以判断正确,但是对于一些特殊的数字将会判断错误。
小费马定理为:若有素数p,则对任意的数a( a为正整数 ,且a < p), a p − 1 ≡ 1 ( m o d   p ) a^{p-1}≡1 (mod \ p) ap11(mod p)。反之,若有任意的a( a为正整数 ,且a < p)使得p不满足 a p − 1 ≡ 1 ( m o d p ) a^{p-1}≡1 (mod \quad p) ap11(modp)p一定为合数。 可以发现若是能够举出所有的a,都能满足上式,是不是就说明p是素数呢?其实不是因为有一类合数也可以做到这一点,这一类合数叫做Carmichael数。前三个这样的数是561, 1105, 1729。这样的数真让人不爽。所以采用这种方法测出来的所谓素数是不一定的。叫做伪素数。哪怕你枚举出所有的a,也不可避免。

更可靠的伪素数测试方法

在小费马定理的基础上有人设计出米勒拉宾随机素数测试法。可以大大的提高检测素数的正确性,但是同样并非一定正确,错误可能性却小到可以接受。
该方法同样利用了枚举多个a的做法,以提高算法的可靠性,对于每一个a,又采用了特殊的方法处理。这基于另外一个定理:如果p是素数,x是小于p的正整数,且 x 2   m o d   p = 1 x^2 \ mod \ p=1 x2 mod p=1,那么要么x=1,要么x=p-1。该定理证明如下:如果p为素数,x是小于p的正整数, 且 x 2   m o d   p = 1 x^2 \ mod \ p=1 x2 mod p=1 ,说明p能够整除**(x+1)(x-1)。但是p是素数,那么只可能是x-1能被p整除(此时x=1**)或x+1能被p整除(此时 x=p-1)。

多次判定

判断一个数是不是素数光靠上面的方法是不可靠的,因为p如果是合数的话,也有可能有 x 2 ≡ 1   m o d ( p ) x^{2}≡1 \ mod (p) x21 mod(p)x=1或者 x =p-1;但是多排除几次p不为合数的话,就增大了p是素数的可能性 ,这是这个算法的核心思想。因此例如341这个数。可知 ( 2 340 ) ≡ 1 ( m o d   341 ) ( 2^{340} ) ≡ 1 (mod\ 341 ) (2340)1(mod 341); ( 2 170 ) ≡ 1 ( m o d   341 ) ( 2^{170} ) ≡ 1 (mod\ 341 ) (2170)1(mod 341); 但是发现 2 85   m o d   341 ≡ 32 2^{85} \ mod \ 341 ≡ 32 285 mod 34132。这足以证明341是一个合数,而不是一个素数。
首先要判断数n是不是2,再判断n是不是奇数。然后尽可能的在令d=n-1,在d中除去2,使得 n = d ( 2 t ) n=d(2^t) n=d(2t),d为奇数,t的值并不关心。如果n是一个素数,那么或者 a d   m o d   n = 1 a^d \ mod \ n=1 ad mod n=1,或者存在某个i使得 a d 2 i   m o d   n = n − 1 , ( 0 ≤ i < r ) a^{d2^i} \ mod \ n=n-1,(0\leq iad2i mod n=n1,(0i<r)(注意i可以等于0,这就把a^d mod n=n-1的情况统一到后面去了)。
a b   m o d ( n ) a^b \ mod(n) ab mod(n)的算法以及求 d 2 d^2 d2的算法是采用的快速幂取模算法。但是在d为long long的情况下有可能乘法溢出。有其他算法可以处理,这里不是重点。

C代码实现

#include 
#include 
using namespace std;
typedef long long LL;

LL mulmod( LL a, LL b , LL p )
{
    LL  d =1;
    a = a%p;
    while( b>0 )
    {
        if(b&1)
            d = (d*a)%p;
        a = (a*a)%p;
        b>>=1;
    }
    return d;
}

bool witness( LL a,LL n)
{
    LL d = n-1 ;
    if( n ==2 ) return true ;
    if( !(n&1) ) return false ;
    while(!(d&1)) d = d/2;
    LL t = mulmod(a,d,n);
    while((d!=n-1) && (t!=1)&&(t!=n-1))
    {
        t = mulmod( t ,2,n);
        d=d<<1;
    }
    return (t==n-1)||(d&1);
}

bool isprime( LL n)
{
    int a[3] = {2,7,61};
    for(int i=0;i<3;i++)
        if(!witness(a[i],n))
            return false;
    return true;
}
int main()
{
    LL s;
    cin>>s;
    if(isprime(s))
        cout<<"YES";
    else
        cout<<"NO";
    return 0;
}

PS:入门蒟蒻,如有错误请指正。
多年后的PS: 大学时的一篇水文,没想到还有人看,再次编辑下公式和排版。(2023/11/20)

你可能感兴趣的:(算法)