一.试除法
根据素数的定义,假设要判断的自然数为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算法又给我们打开了一个新境界,更多的启发,相信在今后的分析研究中,将有更高效的算法出现。