[算法系列]数论01-素数的判定

220 阅读1分钟

素数的判定

数论是算法中一块很重要的内容,而素数的判定则是数论的基础

试除法

试除法是最常用也是最基本的素数判断方法,时间复杂度:O(sqrt(n))

证明:

不妨设b能整除a,那么a/b也一定可以被b整除,那么我们不妨假设a<=a/b,则可以解出a<=sqrt(b)

上面就是试除法的理论基础

代码实现:

inline bool isPrime(int x){
    if(x<2){
        return false;
    }
    for(int i=2;i*i<=x;i++){
        if(x%i==0){
            return false;
        }
    }
    return true;
}

kn+i法

kn+i法是对某些不必要的情况剪支来降低复杂度的算法,时间复杂度:O(n^(1/3))

优化技巧:

我们知道,如果一个整数不是素数,那么该整数或其倍数加上不可能成为素数的倍数也一定不是素数,例如,6不是素数,那么6k+2,6k+3,6k+4,6k+6一定也不是素数,因此,我们就只需要判断6k+1,6k+5的情况即可

代码实现:

bool isPrime(long long x){
    if(n==2||n==3||n==5) return true;
    if(n%2==0||n%3==0||n%5==0||n==1) return false;
    //此时2,3,6已经不可能是素数因子
    long long c=7;
    int a[8]={4,2,4,2,4,6,2,6};
    while(c*c<=x){
        for(int i:a){
            if(x%c==0){
                return false;
            }
            c+=i;
        }
    }
    return true;
}

Miller-Rabin判定法

Miller-Rabin判别法,是目前比较稳定的大数素数判别法,时间复杂度:O(klogn)

理论基础:

该判别法的理论基础是费马小定理,也就是如果p是一个素数,它需要满足对于所有和p互素的正整数a,都有下式:

ap11(mod  p)a^{p-1}\equiv1(mod\;p)

上面的方程是一个同余方程,将他翻译一下,也就是a的p-1次幂减1除以p是一个整数

算法步骤:

1.简单情况特判,n==2 返回true,其余偶数情况舍弃

2.令n=m*(2^k)+1,其中m为奇数

3.枚举小于n的素数p(至多10次),对其进行费马测试

4.费马测试过程如下:计算pre=p^m%n,如果pre==1,测试失效,进行下一个数的测试,否则进行k次计算next=pre^2%n,

如果满足next==1&&pre!=1&&pre!=n-1,则n必定是合数,直接返回即可,k次计算后,如果pre的值不是1,则n必定是合数

5.直到10次测试结束之后,如果都没有检测出n是合数,则n是素数

关于费马测试:

看到这里,很多读者会疑惑这个费马测试是干啥的,下面介绍一下:

所谓费马测试,其实是一种概率型测试,也就是说它判断的是这个数是素数的概率,那么它究竟是拿什么判断的呢?

当然就是我们之前提及的费马小定理,费马小定理说,p是一个素数就满足上述公式

为了解释清楚算法的原理,我们介绍一下二次探测引理

二次探测引理指出:如果p是一个素数,并且0<x<p,那么方程:

x21(mod  p)x^{2}\equiv1(mod\; p)

的解为x=1,或者x=p-1

该引理的证明如下:

易知:x210(mod  p)(x+1)(x1)0(mod  p):(x1)(x+1)p又因为p是素数x=1x=p1易知:x^{2}-1\equiv 0(mod\;p)\\ (x+1)(x-1)\equiv 0(mod \;p)\\ 即:(x-1)(x+1)|p\\ 又因为p是素数\\ x=1或x=p-1

我们在费马测试的过程当中,枚举了小于n的素数p,我们可以这样想:

由于n是奇数,故n1为偶数,不妨设为n1=2t,于是an1=(at)2那么如果at±1(mod  n),我们直接返回是素数就好了,进一步,如果n1=2kt,那么我们反复嵌套上面得过程即可这样一来,从at开始得序列就只有可能是:±1,1,1,1...或者:±1,±1...1(k个必须是1)其余情况直接返回合数即可由于n是奇数,故n-1为偶数,不妨设为n-1=2t,于是a^{n-1}=(a^{t})^{2}\\ 那么如果a^{t}\equiv \pm1(mod\;n),我们直接返回是素数就好了,进一步,如果n-1=2^{k}t,那么我们反复嵌套上面得过程即可\\ 这样一来,从a^{t}开始得序列就只有可能是:\\ \pm1,1,1,1 ...\\ 或者:\\ \ne\pm1,\ne\pm1...-1(第k个必须是-1) 其余情况直接返回合数即可

由于费马小定理只是一种必要条件而非充要条件,因此存在极少数得特例,这些特例也被叫做“绝对伪素数”

代码实现:

typedef long long ll;
ll quick_multiply(ll a,ll b,ll m){  //快速乘,取模为m
    ll ans=0,temp=a;
    while(b){
        if(b&1){
            ans=(ans+temp)%m;
        }
        temp=(temp+temp)%m;
        b>>=1;
    }
    return ans;
}
ll quick_pow(ll a,ll b,ll m){
    ll ans=1,temp=a;
    while(b){
        if(b&1){
            ans=quick_multiply(ans,temp,m);
        }
        temp=quick_multiply(temp,temp,m);
        b>>=1;
    }
    return ans;
}
ll Miller_Rabin(ll n){
    if(n==2) return true;
    if(n<2||!(n&1)) return false;
    int k=0,t=n-1;
    while(!(k&1)){
        k++;
        t>>=1;
    }
    for(int i=0;i<=10;i++){
        ll a=rand()%(n-1)+1;
        ll b=quick_pow(a,t,n);
        ll y;
        for(int i=0;i<k;i++){
            y=quick_multiply(b,b,n);
            if(y==1&&b!=1&&b!=n-1){
                return false;
            }
            b=y;
        }
        if(y!=-1){
            return false;
        }
    }
    return true;
}