素数与素数检测

一.试除法

根据素数的定义,假设要判断的自然数为n,那么最简单的方法便是用2~(n-1)之间的数字依次枚举试除一遍,如果能整除,那说明这个数不是素数,显然,此种算法的效率极低。初学C语言的人会使用另一种改进的试除法,那便是除数不用取遍2~(n-1),而是取2~(int)sqrt(n),但是当n很大时,此种算法的时间消耗也很大,也是不可取的。


二.筛选法

筛选法事一种比较高效的判断素数的方法,能够一次性的筛选除某个区间的素数。算法的基本原理也是利用了素数的定义,在某个范围内,依次去掉2的倍数,3的倍数,5的倍数……以此类推,一直到所有小于或等于n的数字的倍数都被去掉为止。这就像一面筛子,把某个区间范围内满足条件的数留下来,其余的数字去掉,最后判断此区间内的某个数是否为素数时,时间复杂度就为O(1)。很显然,时间的主要消耗都在于数字的筛选上。利用给数组单元置零的方法来实现,创建一个数组a[i],i=1、2、3……,用memset()函数将其全部置1,当i不是素数,则将a[i]置零

#define MAX N  /*N为设置的某个数,确定一个判断区间*/
int isprime[MAX];
void is_prime1()
{
    int i,j;
    memset(isprime,1,sizeof(isprime));

    for(i=2;i<MAX;i++) //这里的MAX可以改成 <=MAX/2 或 <=sqrt(MAX)
    {
        if(isprime[i])
           for(j=2*i;j<MAX;j+=i)
              isprime[i]=0;
    }
}

二次优化代码:

#include<stdio.h>
#include<math.h>
#define N 10000001   //不打印素数,1千万(8位数)以内素数,用时0.25s 一亿以内素数用时2.6s
bool prime[N];
int main()
{
   int i, j;
   prime[2] = true;
   for(i=2; i<N; i++)
     if(i%2)
       prime=false;
    else 
       prime=true;
   for(i=3; i<=sqrt(N); i+=2) //这里不要改成 i*i <= N ,原因本来每次只要计算一次sqrt(),改后会对j的循环每次计算j*j
   {   if(prime)
       for(j=i+i; j<N; j+=i)

            prime=false;
   }
//输出部分素数
  for(i=2; i<100; i++)
    if( prime )

         printf("%d ",i);
   return 0;
}

以上还可以继续优化,将筛选法进一步改进为真正的线性时间复杂度,改进算法是增加了一个数组,记录已经找到的素数,通过这些已经找到的素数,来筛掉后面的数。


三.费马测试

费马算法是利用数学结论来进行素数检验的方法,利用了费马定理得到推论,若n>1,存在,使得则是合数。费马算法正是利用这个推论判断合数,对于素数的判断和可靠性的判断依据以下定理:

定理:或者所有的或者之多一半的满足  和(a,n)=1的整数a满足

费马测试是判断一个数是否为素数的一个基于概率的测试。费马测试的具体实现是,对于n,从素数表中取出任意的素数对其进行费马测试,如果取了很多个素数,n仍未测试失败,那么则认为n是素数。当然,测试的次数越多越准确,但一般来讲50次就足够了。另外,预先构造一个包括500个素数的数组,先对n进行整除测试,将会大大提高通过率。

代码实现如下:

int montgomery(int n,int p,int m)
{
    int k=1;
    n%=m;
    while(p!=1)
    {
        if(0!=(p&1))
            k=(k*n)%m;
        n=(n*n)%m;
        p>>1;
     }
     return(n*k)%m;
}
void prime(int n)
{
     np=0;
     for(int i=2;i<=n;i++)
     {
         if(!isprime[i])p[np++]=i;
     for(int j=0;j<np&&p[j]*i<=n;j++)
     {
         isprime[p[j]*j]=1;
         if(i%p[j==0])break;
      }
      }
}
bool isprime(int n)
{
     if(n<2)return false;
     for(int i=0;i<np;++i)
     {
         if(1!=montgomery(p[i],n-1,n))
         {
             return false;
          }
      }
      return true;
}
同样这里有一份素数检验测试代码,和费马测试没什么太大关系,思想差不多,对比一下

#include<iostream>
#include<cmath>
using namespace std;
bool Jude(int n)
{
    int i;
    if(n==2||n==3)
        return true;
    else if(n<2)
        return false;
    else
    {
        for(i=2;i<=sqrt(1.0*n);i++)//这里sqrt(1.0*n)就算了一次,
        //如果判断条件改为i*i<=n,这里的i*i就会做sqrt(n)次,每次循环都要算一次,会超时
            if(n%i==0)
                return false;
        return true;
    }
}
int main()
{
    int t,a;
    int sum;
    while(~scanf("%d",&t))
    {
        sum=0;
        while(t--)
        {
            scanf("%d",&a);
            if(Jude(a)) 
                sum++;
        }
        printf("%d\n",sum);
    }
    return 0;
}

优化的代码,下面是上面的效率的大约20倍

#include<cstdio>
#include<cmath>
int pr[8]={4,2,4,2,4,6,2,6};  //对应每次加相应的数 得出全部素数和部分合数(7+ ->) 11 13 17 19 23 29 31 37  41 43..
int prime(int n)
{
    int i=7,j;
    if(n<2)
        return 0;
    if(n==2||n==3||n==5) 
        return 1;
    if(!(n%2&&n%3&&n%5))  //在进行检验是否为素数的过程中,2 3 5 的倍的数所占比较多,优先判断
        return 0;
    for(;i<=sqrt(n);)
    {
     //不断的加数组里德元素后相应数后生成树,用于测试检验数是否为素数 但有合数,检验相率不能达到最高,但是代码简单
        for(j=0;j<8;j++)  
        {
            if(n%i==0)
             return 0;
            i+=pr[j];
        }
        if(n%i==0)
         return 0;
    }
    return 1;
}
int main()
{
    int i,n,m,s;
    while(scanf("%d",&n)!=EOF)
    {
        s=0;
        for(i=0;i<n;i++)
        {
            scanf("%d",&m);
            if(prime(m))
                s++;
        }
        printf("%d\n",s);
    }
    return 0;
}
对于上面的代码,还可以优化:

先将大量的素数存入数组,不写在代码里面,可以先用其他代码生成素数,在复制到数组里面,那么在上面代码注释的那附近,改一下测试需要的数,保证前面你测试的都是你早生成数组的素数,避免的部分合数的测试,前面数组试无完了后,再综合上面的加法数组元素,进行测试。


四.  米勒-拉宾测试

米勒拉宾测试是一个不确定的算法,只能从概率意义上判定一个数可能是素数。

      输入奇数n,判断n为素数或者合数。

      步骤:

1.  计算r和R,使得,R奇。

2.  随即选择a,

3.  for i=0 to r,计算

4.  若,则输入合数。

5.  若,则输入素数。

6.  设j=max{i,则输入素数。

7.  若,则输出素数,否则输出合数。

代码如下:

#include<iostream>
using namespace std ;
__int64 qpow(int a,int b,int r)
{
    __int64 ans=1,buff=a;
    while(b)
    {
        if(b&1)
            ans=(ans*buff)%r;
        buff=(buff*buff)%r;
        b>>=1;
    }
    return ans;
}
bool Miller_Rabbin(int n,int a)
{
    int r=0,s=n-1,j;
    if(!(n%a))
        return false;
    while(!(s&1))
    {
        s>>=1;
        r++;
    }
    __int64 k=qpow(a,s,n);
    if(k==1)
        return true;
    for(j=0;j<r;j++,k=k*k%n)
        if(k==n-1)
            return true;
    return false;
}
bool IsPrime(int n)
{
    int tab[5]={2,3,5,7};
    for(int i=0;i<4;i++)
    {
        if(n==tab[i])
            return true;
        if(!Miller_Rabbin(n,tab[i]))
            return false;
    }
    return true;
}
int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        int ans=0,a;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a);
            if(IsPrime(a))
                ans++;
        }
        printf("%d\n",ans);
    }
    return 0;
}

五.   AKS算法

 2002年提出的AKS算法,利用了代数数论、有限域中的深刻结论,是一个确定性的多项式算法。ASK算法描述如下:

输入奇数n,判断n为素数或者合数。

步骤:

1.  输入n>1;

2.  若,输出合数;

3.  找r>0,满足

4.  任意的0 < a <= r,计算(a,n),如果n>(a,n)>1,则输出合数;

5.  n <= r,输出素数;

6.  FOR a=1 to  ,输出合数。

7.  输出素数。

以上的介绍可见,素数的判断有许多方法,在数字规模不同的情况下,可以选择不同的算法。筛选法容易理解,一定程度上效率得到提高,可是却受到内存的限制,而米勒拉宾算法是一种不确定算法,却是高效算法。AKS算法又给我们打开了一个新境界,更多的启发,相信在今后的分析研究中,将有更高效的算法出现。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值