【算法】素数筛和线性筛(埃氏筛,欧拉筛)

备注

2020/3/7星期六
最近现闲家里突然看到了一个关于求素数(也称质数)的文章,里面讲解了埃氏筛和欧拉筛求素数,很多时候求素数是非常重要的,好的算法可以大大提高程序的运行效率。

一、常规求法

1.原理和优化

求素数最直接的思路就是枚举,判断n是否为素数只需把小于n的数字依次尝试,看n能否被整除,但是实际上我们只需要判断到 n \sqrt{n } n 即可,因为如果n不是素数,那么n的因数除 n \sqrt{n } n 外必然成对存在,且一个比 n \sqrt{n } n 大另一个比 n \sqrt{n } n 小,所以求到 n \sqrt{n } n 即可(但是为了方便在 程序中一般用i*i<=n来判断)。这种方法常用于判断个别数字是否为素数。

2.算法实现

bool prime(int n){
    
    
	for(int i=2;i*i<=n;i++){
    
    
		if(n%i==0) return false;
	}
	return true;
}

3.时间复杂度

我们很容易看出来,这个是算法的时间复杂度是O( n \sqrt{n } n )


局限:
当我们需要得到自然数n以内的全部素数时,这种方法就不再好用。


二、埃氏筛

1.原理

埃拉托斯特尼(Eratosthenes)筛法,简称埃氏筛或爱氏筛,是一种由希腊数学家埃拉托斯特尼所提出的一种简单检定素数的算法。为了得到自然数n以内的全部素数,必须把不大于根号n的所有素数的倍数剔除,剩下的就是素数。给出要筛数值的范围n,找出以内的素数。先用2去筛,即把2留下,把2的倍数剔除掉;再用下一个质数,也就是3筛,把3留下,把3的倍数剔除掉;接下去用下一个质数5筛,把5留下,把5的倍数剔除掉;不断重复下去…。这个过程我们一般建立一个标志数组用来保存某个数字是否被剔除。


思考:
在我看来,剔除非素数的过程实际是一个创造合数的过程,由于算数基本定理(每一个合数都可以以唯一形式被写成质数的乘积,即分解质因数。)我们只要将已知的质数乘以自然数(从1到n)就可以“创造”出合数,而在这个过程中没有被创造出来的数字就是质数,我们再用新的质素重复这个过程,直达找出n以内的所有质素。

2.算法实现

bool num[MAX]={
    
    0};	//数字是否被剔除
int primenum[MAX];	//存放素数
void prime(int n){
    
    
	for(int i=2;i*i<=n;i++){
    
    
		if(!num[i]){
    
    			//如果数字i是素数
			for (int j=i*i; j<=n; j+=i){
    
    	//剔除i的倍数(这里从i*i开始比从2*i开始节省时间,因为更小的数字已经被剔除过一次了)
				num[j*i]=true;	
			}
		}
	}
	for(int i=2;i<=n;i++){
    
    			//现在num数组值为0的数字就是素数,我们把他们存到primenum中
		if(!num[i]) primenum[t++]=i;
	}
}

3.时间复杂度

埃氏筛的时间复杂度是O(nloglogn),推导过程可自行搜索。


局限:
对于一个非素数我们进行了多次判断,造成浪费。


三、欧拉筛

1.原理

为了解决多次判断浪费时间的问题,欧拉筛便诞生了。在埃氏筛法的基础上,让每个合数只被它的最小质因子筛选一次,以达到不重复的目的。为此我们需要一个数组来存储素数,然后用每个自然数
依次乘以已知的素数,并在这个过程中进行一些判断,以达到用最小素数筛选的目的。


思考:
与埃氏筛相同,欧拉筛也是一个创造合数的过程,但是对于每一个合数,我们只创造一次,就是通过他的最小质因数乘以他的最大因数(除自己外)这种方式实现的。而对于这个最大因数,它可能是质数也可能是合数,当它是质数时,由于算数基本定理(每一个合数都可以以唯一形式被写成质数的乘积,即分解质因数。),那么我们构造出来的合数一定是新的(第一次被我们的程序构造);当它是合数时,由于算数基本定理,我们可以把这个合数看做是构成它的质因数的乘积,为了保证这次构造的合数是一个新的,我只需要将已知的质数按从小到大依次对合数求余,当合数被某个质数整除时,小于等于这个质数的质数都可以和这个合数构造一个新的合数,而大于这个合数的质数则不能。此时我们开始下一轮构造即可。因为当我们使用的质数比已知合数分解得到的质数大时,我们就已经通过更小的质数构造过这个合数了。

2.算法实现

int t=0;			//已求出的质数的个数
int primenum[MAX];	//质数列
bool num[MAX]={
    
    0};	//判断是否被剔除
void prime(int n){
    
    
	for(int i=2;i<=n;i++){
    
    
		if(!num[i]){
    
    	//如果i没有被剔除,就加入质数列
			primenum[t++]=i;
		}
		for(int j=0;j<t;j++){
    
    	//构造合数
			if(i*primenum[j]>n) break;	//构造的合数大于需要的范围n
			num[i*primenum[j]]=true;	//剔除构造的合数
			if(i%primenum[j]==0) break;	//数字i可以分解出与当前素数相同的素数,我们就不能继续使用更大的素数了
		}
	}
}

3.时间复杂度

欧拉筛是一个O(n) 级的算法,实在是天才般的智慧。


欢迎在评论区提出你宝贵的意见

猜你喜欢

转载自blog.csdn.net/l1447320229/article/details/104717087