素数判定的一些讨论(Miller-Rabin算法)

一类问题: 判定一个整数n(n>1)是否为素数。

算法一:

直接根据素数的定义枚举从到,如果n%i==0n为合数。

时间复杂度:O(n)

int is_prime(int n)
{
    int i;
    for(i = 2; i < n; i++)
        if(n % i == 0) return 0;
    return 1;
}

算法二:

发现若存在使得n%i==0,则必有n%(n/i)==0
所以只需枚举从到即可。

时间复杂度:

int is_prime(int n)
{
    int i;
    for(i = 2; i * i <= n; i++) if(n % i == 0)
            return 0;
    return 1;
}

如果要找到成1~n的所有素数那么这个时间代价就变为O(n^2),很多时候是不可接受的。

所以随着学习的深入,我们了解到了素数筛法,即从2开始,2的倍数肯定不是素数,再向右扫描,如果扫描到素数,则重复之前的过程,剔除之后的部分合数(准确的说是关于当前质数的倍数),如果扫描到合数则跳过(表示前面已经更新过这个数不是素数)。然后都扫描一遍即可把1~n的素数求解出来。这个算法的复杂度略高于O(n)。素数筛代码如下:

#include  
#include  
using namespace std;  
  
const int MAXN = 500000;  
  
bool isprime[MAXN];  
int prime[MAXN];  
int cnt = 0;//保存素数个数  
void getPrime()  
{  
    for(int i = 1; i < MAXN; i++)  
        isprime[i] = true; //先假设所有数是素数,后面逐个扫描更新  
    for(int i = 2; i < MAXN; i++) //扫一遍  
    {  
        if(!isprime[i]) continue; //如果不是素数,则不往后面更新  
          
        prime[cnt++] = i;  
        for(int j = 2 * i; j < MAXN; j += i)  
            isprime[j] = false;  
    }  
}  
  
int main()  
{  
    getPrime();  
    for(int i = 0; i < cnt; i++)  
        cout << prime[i] << endl;  
}  

但是这个算法的弊端在于,为了判断一个大数是否是素数必须从从头开始扫描,而且空间代价也受不了,故不能离散的判断。

素数判定的一些讨论(Miller-Rabin算法)_第1张图片

看完了上面的引理,那就可以正式开始Miller-Rabin算法的讲解了。

背景:

素性测试(即测试给定的数是否为素数)是近代密码学中的一个非常重要的课题。虽然Wilson定理(对于给定的正整数n,n是素数的充要条件为)给出了一个数是素数的充要条件,但根据它来素性测试所需的计算量太大,无法实现对较大整数的测试。目前,尽管高效的确定性的素性算法尚未找到,但已有一些随机算法可用于素性测试及大整数的因数分解。下面描述的Miller-Rabin素性测试算法就是一个这样的算法。

算法:

 

首先要知道费马定理只是n是素数的必要条件。即费马定理不成立,n一定是合数;费马定理成立,n可能是素数。接下来请看Miller-Rabin算法的分析过程。

素数判定的一些讨论(Miller-Rabin算法)_第2张图片

 

#include
using namespace std;
typedef long long ll;
ll multi(ll a,ll b,ll p)
{
    ll temp=0;
    while (b)
    {
        if (b&(ll)1) temp=(temp+a)%p;
        a=(a<<1)%p;
        b>>=1;
    }
    return temp;
}

ll FastPower(ll a, ll p, ll k)
{
    ll ans = 1;
    while (p)
    {
        if (p & 1) ans = multi(ans,a,k);
        a = multi(a,a,k);
        p >>= 1;
    }
    return ans;
}
bool judge(ll a, ll n, ll m, ll r)
{
    ll x = FastPower(a, m, n);
    if (x == 1 || x == n - 1) return true;
    while (r--)
    {
        x = multi(x,x,n);
        if (x == n - 1) return true;
    }
    return false;
}
bool Miller_Rabin(ll n)
{
    if (n == 2) return true;
    if (n == 1 || !(n & 1)) return false;
    ll m = n - 1, r = 0;
    while (!(m & 1))
    {
        r++;
        m >>= 1;
    }
    for (int i = 0; i < 10; i++)
    {
        int a = rand() % (n - 1) + 1;
        if (!judge(a, n, m, r))
            return false;
    }
    return true;
}
int main ()
{
    ll n;
    while (scanf ("%lld",&n)!=EOF)
    {
        if (Miller_Rabin(n)) printf ("%lld is a prime\n",n);
        else printf ("%lld is not a prime\n",n);
    }
    return 0;
}

#include
using namespace std;
typedef long long ll;
const int num[12]= {0,2,3,5,7,11,13,17,19,23,29};
ll t,ans;
ll n;
ll ksc(ll a,ll b,ll p)
{
    ll ans=0;
    while(b)
    {
        if(b&1) ans=(ans+a)%p;
        b>>=1;
        a=(a+a)%p;
    }
    return ans;
}
ll ksm(ll a,ll b,ll p)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=ksc(ans,a,p);
        a=ksc(a,a,p);
        b>>=1;
    }
    return ans;
}
bool Miller_Rabin(ll n)
{
    ll u=n-1,t=0;
    if(n==2) return 1;
    if(!(n&1) || n==1) return 0;
    for(; !(u&1); u>>=1,t++); //n-1=2^t*u
    for(ll i=1; i<=10; i++)
    {
        if(num[i]==n) continue;
        ll a=num[i],x=ksm(a,u,n);
        for(ll i=1; i<=t; i++)
        {
            ll y=ksc(x,x,n);
            if(y==1 && x!=1 && x!=n-1) return 0;
            x=y;
        }
        if(x!=1) return 0;
    }
    return 1;
}
int main ()
{
    ll n;
    while (scanf ("%lld",&n)!=EOF)
    {
        if (Miller_Rabin(n)) printf ("%lld is a prime\n",n);
        else printf ("%lld is not a prime\n",n);
    }
    return 0;
}

这里有另一个版本的解释

 

 

 

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