筛法求素数

筛法求素数

首先我们知道任何一个合数都可以由素数的乘积得到,如果我们将得到的素数进行乘积运算标记合数那么就会节省很多时间。而标记的这一环节就是筛法名称的由来。

#include<iostream>
#include<string.h>
#define max_size 100000
using namespace std;

//Isprime[]来标记是否是素数  1为是 0为否
int Isprime[max_size];
//记录素数
int prime[max_size];

int main()
{
    //n表示计算[0,n]内的素数  p第几个素数从0算起
	int n,p=0;
	cin>>n;
	//将所有数先标记为是素数
	memset(Isprime,1,sizeof(Isprime));
	//0,1不是素数;
	Isprime[0]=0; Isprime[1]=0;
	//从2开始循环	
	for(int i=2;i<=n;i++)
	{
		//如果i是素数
		if(Isprime[i])
		{
		//记录当前 i 为素数     素数个数 p 增加 1
		prime[p++]=i;
		//计算当前素数的所有倍数(小于等于设置的最大值)那么其都为合数
		for(int j=2;prime[p-1]*j<=max_size;j++)
		{
			// 计算为合数的下标 标记其Isprime为0; 
			Isprime[prime[p-1]*j]=0;
		}
		}
	}
	for(int i=0;i<p;i++)
	{
		//输出素数
		cout<<prime[i]<<" ";
	}
}

这个方法可以进一步优化。我们可以先简单的列一个表格:

prime下标0123
乘数\质数2357
22x22x32x52x7
33x23x33x53x7
44x24x34x54x7
55x25x35x55x7
66x26x36x56x7
77x27x37x57x7

我们发现 2x3和3x2 2x5和5x2 以及3x5与5x3等等这些数倒过来又被筛了一边 而4x5这种情况会被2x10筛去,我们发现是不是可以从当前素数为大小的素数直接开始乘起就可以避免这种情况,我们可以先写一个代码验证一下我们的猜想。

改进后的筛法代码如下:

	for(int i=2;i<=n;i++)
	{
		if(Isprime[i])
		{
		prime[p++]=i;
		for(int j=p-1;prime[p-1]*j<=max_size;j++)
		{ 
			Isprime[prime[p-1]*j]=0;
		}
		}
	}

由于数据太多我就不贴了,大家可以自行验证一下答案是正确的。

这时我们再看表格我们进行的运算为:

prime下标0123
乘数\质数2357
22x2
33x23x3
44x24x3
55x25x35x5
66x26x36x5
77x27x37x57x7

我们可以看到已经省去了很多运算。但是我们仍可以看出,这里面有重复的运算,例如 6x2 与 4x3 以及没列出的 15x3 与 9x5 显然重复筛去了,而且我们仍有疑问为什么上个代码的6x7等表中没有重复的不用算,这是应为当乘数为合数时可以拆成更小的质数,列如 6 拆成 2x3 ,显然当质数为 2 时,2与所有乘数得到的乘积,包含了,乘数6与所有质数相关的乘积(乘积均在max_size内)。

归纳:所有为合数的乘数必会被其素数因子多次筛去。但一个合数一定只有一个最小素数因子。只要只被最小素数因子筛去一次,就可以达到线性时间,这种筛法也被称为线性筛法或欧(欧拉)筛法。

		for(int i=2;i<=n;i++)
		{
			if(Isprime[i])
			{
				prime[p++]=i;
			}
			//前两次筛法我们的循环是表格中每一列的循环筛去,
			//而这次我们需要剔除当前乘数的一行计算所以每次循环为一行。
			//那就要保证当前循环不超过p(当前素数的个数)
			for(int j=0;j<p && prime[j]*i<=max_size;j++)
			{
				Isprime[prime[j]*i]=0;
				//如果乘数的最小素因子在质数中出现。
				//那么就表明质数一列计算会包含当前乘数i的所有计算.
				//跳出循环避免重复运算。
				if(i%prime[j] == 0)
				{
					break;
				}
			}	
		}

再看表格运算就变为:

prime下标0123
乘数\质数2357
22x2
33x23x3
44x2
55x25x35x5
66x2
77x27x37x57x7

又简便了许多。

萌新第一次写题解,若有不对请及时指正,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值