素数判断方法大合集

一、 朴素判断素数  

根据素数的定义,约数只有1和它本身的整数称为素数,假设一个整数为n,于是最朴素的判断n是否为素数的方法就是从2到n-1都枚举一遍,判断是否存在能整除n的整数,如果都不能则n为素数。 代码实现如下:

bool Brute_Force(int n)  
{   
	for (int i=2;i<=n-1;i++)    
		if (n%i==0) 
			return false;  
	return true; 
} 
此函数返回true则说明n为素数,反之不是。  很容易发现,这种方法判断素数,对于一个整数n,需要n-2次判断,时间复杂度为O(n)的。在n非常大或者测试量很大时,这种方法显然是不可取的。


二、 改进朴素判断素数 
对于一个小于n的整数x,如果n不能整除x,则n必然不能整除n/x。反之相同。所以我们按照素数定义来判断素数时,可以进行一个较为明显的优化。即我们只需从2枚举到√n即可。因为在判断2的同时也判断了n/2……以此类推,到√n时就把2到n-1的数都判断过了。

代码实现如下:

bool Brute_Force2(int n)
{  
	for (int i=2;(__int64)i*i<=n;i++)  
		if (n%i==0)  
			return false;  
	return true; 
}
这里使用i*i<=n来取代i<=√n 是为了避免是用sqrt()函数,其消耗时间很大,在大量数据测试中时间消耗很明显。同时强制转换i成__int64类型是为了防止i*i在int范围内溢出。此算法的时间复杂度也很容易得出,对于一个整数n,需要测试√n-1次,所以本算法的时间复杂度为O(√n)的。

三、 标准的爱拉托逊斯筛选法

爱拉托逊斯筛选法(以下简称筛法),是一种高效的判断素数的方法。能够一次性的筛选出某个区间的素数。其算法原理本质还是充分利用了素数的定义,即素数的约数只有1和它本身。如果某个数m是另一个数n的倍数,则说明m肯定不是素数。所以我们只要在某个范围内,将已知素数的所有倍数都筛去,剩下的肯定是素数。因为只有它不是其他数的倍数(1和本身除外)。  具体做法是:先把N个自然数按次序排列起来。1不是质数,也不是合数,要划去。第二个数2是质数留下来,而把2后面所有能被2整除的数都划去。2后面第一 个没划去的数是3,把3留下,再把3后面所有能被3整除的数都划去。3后面第一个没划去的数是5,把5留下,再把5后面所有能被5整除的数都划去。这样一 直做下去,就会把不超过N的全部合数都筛掉,留下的就是不超过N的全部质数。因为希腊人是把数写在涂腊的板上,每要划去一个数,就在上面记以小点,寻求质 数的工作完毕后,这许多小点就像一个筛子,所以就把埃拉托斯特尼的方法叫做“埃拉托斯特尼筛”,简称“筛法”。(另一种解释是当时的数写在纸草上,每要划 去一个数,就把这个数挖去,寻求质数的工作完毕后,这许多小洞就像一个筛子。)

代码实现如下:

#define MAX 10007
bool isprime[MAX];  
void TheSieveofEratosthees() 
{   
	int i,j;  
	for (i=2;i<MAX;i++)    
		isprime[i]=1; 
	for (i=2;i<MAX;i++)  
	{     
		if (isprime[i])    
			for (j=i+i;j<MAX;j+=i)   
				isprime[j]=0; 
	}
} 

在执行完本算法后,isprime[i]=1则说明i是素数。所以本算法在执行完一遍后,就能在O(1)的时间复杂度内判断MAX以内的任意数是否为素数。所以整个算法的时间消耗都在筛法的效率上。乍看筛法的时间复杂度貌似是O(n^2)的,但是其实不然,第二个循环中,每次递增的i,当i越来越大时,j很快就能超过M。其实筛 法的实际复杂度是的,在可以测试的范围内,其实是 接近线形的,虽然实际上不是。这个是筛法的精妙所在。


四、 改进的爱拉托逊斯筛选法

理论上筛法在可以测试的范围内,已经接近线性的复杂度了,对于一般的需要来说,已经没有什么必要去优化筛法了。但是为了更深入或者满足更苛刻的效率要求,标准的筛法还是有可以改进的地方的,使得筛法在常数级别上得到降低。实际上在2007年,复旦的xreborner已经将筛法改进为真正的线性时间复杂度。该改进算法是增加了一个数组,记录已经找到的素数,通过这些已经找到的素数,来筛掉后面的数,由于每个数都能分解成质因数的形式,所以所有质因数都被筛掉后,自然不在素数列表中了。

 代码实现如下:

#define MAX 10007 
bool isprime[MAX]; 
int p[MAX];  
void prime(int n) 
{    
	memset(isprime, 0, sizeof isprime);   
	memset(p, 0, sizeof p);    
	int 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]*i] = 1;       
			if( i % p[j] == 0) break;      
		}    
	} 
}
这个算法的关键在于 if(i%pr[j] == 0) break;。它使得任何一个合数,只被它最小的质因数标记过一次。所以整个算法是线性的。但考虑到log(log(100000000))还不到3,故这个线性算法其实也只有理论上的价值罢了。


六、 费马素性测试  

费马小定理说的是:如果p是一个素数,那么对于任意一个整数a,a p − a 能被p整除,也可以用模运算表示如下:    (p是素数,a是整数)  这个定理又如下变式:如果p是一个素数,且整数a与p互素,那么 a p−1 − 1 可以被p整除,用模运算表示如下

a^(p-1) 模等于 1(mod p)

(p是素数,a是整数,a与p互素)、 

 还有一种表述是:如果p是一个素数,a是一个整数且a不包含因数p,那么 a p−1 − 1 可以被p整除。   费马小定理是费马素性测试的基础。  费马在给出此定理的时候未给出证明,第一个证明其的人是Gottfried Leibniz。 费马素性测试是判断一个数是否为素数的一个基于概率的测试。事实上,费马小定理的逆否定理成立,而费马小定理的逆定理是不成立的,而费马素性测试就是基于费马小定理的“逆定理”的。  大概的算法描述是,当p为奇数时(偶数特判一下就行啦,不就一个2嘛)让a在1-p之间(包括1和p)选取随机值,如果等式不成立,那么p肯定不是素数,如果成立,那么p就有较大可能是素数,我们称他为伪素数。当然,费马素性测试是有极大缺陷的,因而基本上平时没有多大用武之地。一个缺陷就是Carmichael数的存在,Carmichael数是指如果一个数n可以通过所有‘a’值的费马素性测试却并非为素数,那么就叫n为Carmichael数。这样的数随着n的增大而越来越少的,这些数中,最小的一个是561.  费马测试的具体实现是,对于N,从素数表中取出任意的素数对其进行费马测试,如果取了很多个素数,N仍未测试失败,那么则认为N是素数。当然,测试次数越多越准确,但一般来讲 50次就足够了。另外,预先用“小学生”的算法构造一个包括500个素数的数组,先对Q进行整除测试,将会大大提高通过率。 

代码实现如下:

int Montgomery(int n,int p,int m) 
{
	//快速计算(n^e)%m的值,即逐次平方法     
	intk=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]*i] = 1;         
			if( i % p[j] == 0) break;      
		}    
	}
}  
bool IsPrime3(int n) 
{     
	if ( n < 2 )
	{  
		// 小于2的数即不是合数也不是素数    
		return false;   
	}    
	for (int i=0;i<np;++i)    
	{  
		// 按照素数表中的数对当前素数进行判断  
		if (1!=Montgomery(p[i],n-1,n))//蒙格马利算法       
		{            
			return false;       
		} 
	}     
	return true;
}


the end~


展开阅读全文

没有更多推荐了,返回首页