测试素数的方式很多,本文将介绍一些算法,能尽快的判断一个给定的数字是否为素数。
判断给定数字n是否是素数,一个比较简单的方法就是:式除。试着用2,3,... 直到根号n去除n。显然,如果n是素数,则没有一个数能整除n。
一个比较好的改进是,只用2到根号n的素数做为除数,这样可以减少不少除法的次数,算法的代码可以表示为:
bool checkPrime (int n)
{
int max = static_cast
int i=2;
while (i<=max)
{
if (n%i == 0)
{
return false;
}
i++;
}
return true;
}
但是这个算法的特点是,它不仅能够判断n是否为素食,同时,如果n为合数,该算法还能给出这个合数的一个因子。但是这个算法的效率不高。我们有什么办法,在不找出因子的情况下,能够判断n是否为素数。
以下这个公式是一个几乎可行的判断方法:
an-1 = 1(mod n)
其中,a表示基数,是一个大于1的自然数。如果这个等式不满足,则可以肯定,n是个合数。如果这个等式满足,则n是一个素数,或者是伪素数。由此可见,这个算法存在一定的误差。我们先用几个数字试试看:
假设我取基数a等于2。等式可以写成:
2n-1 = 1(mod n)
将3带入,左边等于4,4 mod 3 = 1,正好等于右边,所以3为素数。通过尝试,发现5,7,11,13都满足这个等式。看上取不错,因为这个计算过程要比之前提到的式除法简单多了,但是慢着,试试341,561,645,1105,你发现这个式子突然不灵了。341,561,645,1105这几个数字,虽然满足等式,但是都式合数,这四个数字就是基于2的伪素数。所以这个算法存在一定的误差。
很遗憾,即使选择其它基数(比如3,5),仍旧存在一些伪素数不能被检查出来。这些数字被称为Carmichael。其实Carmichael数很少,在小于100 000 000的数字中,Carmichael数只有255个。这个算法的代码如下:
bool checkPrime(int n,int a)
{
int t = pow(a,n-1);
if (t%(n)==1)
{
return true;
}
else
{
return false;
}
return false;
}
下面将介绍Miller-Rabin算法,来减少出错的概率。
Miller-Rabin算法的改进主要有一下两点:
1)使用随机选取多个基数,而不是只选取2
2)使用辅助过程,来判断是否为合数
witness的算法大致如下:
首先,将n-1写成2tu的形式。其中,u必须是奇数。比如:
n=561 n-1 = 560 = 24*35
然后计算X0 = a u mod n
循环,i从1到t,计算Xi = X2i-1 mod n
如果Xi等于1,而且Xi-1不等于1而且Xi-1不等于n-1,则返回true,表示找到合数
当循环结束,Xt不等于1,则返回true,表示合数
否则,返回false,表示该数为素数。
现在大致介绍一下代码:
bool checkPrime(int n,int s)
{
int j;
for (j=1;j
{
int a=rand();
if (witness(a,n))
{
return false;
}
}
return true;
}
代码的结构很简单,参数s表示循环的次数,每次循环,都会随机得到一个值作为基数。然后调用witness验证是否为合数。
虽然Miller-Rabin的结果仍旧有可能产生误差,但是这个误差是可以接受的。该误差的存在概率很小,且这个误差的可能性并不依赖于n,而是取决于s的大小和随机数a的运气。总之,错误发生的概率很小。