从零优化 素数筛——详解

算法不是记模板,而是启发思维

判断一个数是否是素数,首先我们明晰素数的概念。

  • 素数:素数一般指质数。质数是指在大于1的自然数中,除了1和它本身以外不再有其他因数的自然数。

暴力

由此我们可以从2遍历到x-1,判断x能否被整除,写出最简单暴力的IsPrime(int x)

bool IsPrime(int x)
{
	if(x==1)return false;
	bool flag=true;//标记是否是素数
	for(int i=2;i<x;++i)
	{
		if(x%i==0)
		{
		flag=false;
		break;
		}
	}
	return flag;
}

显然,单次查询的复杂度为O(n)。
但如果x>1e12,一般都会超时

暴力优化

接下来,我们对其进行一次优化
如果x能被2到x-1其中的一个数k整除,定义k’ =x/k,x也一定能被k’整除,x在产生一个因数的同时会产生另一个因数。

并且我们发现一个神奇的现象,就是k和k’中一个<=sqrt(x)一个>=sqrt(x),成对出现的因数分别分布在<=sqrt(x)的一侧和>=sqrt(x)的一侧,那么也许我们就不需要从2遍历到x-1来查找x的因数,只需要从2遍历到sqrt(x)就能x判断是否是素数

给出代码

bool IsPrime(int x)
{
	if(x==1)return false;
	bool flag=true;
	int end=sqrt(x);
	for(int i=2;i<=end;++i)
	{
		if(x%i==0)
		{
		flag=false;
		break;
		}
	}
	return flag;
}

这样的时间复杂度为O( x \sqrt{x} x ) 即使x在1e12附近,也能快速得出

上面我们讨论的是单次查询的情况,如果是多次判断x,判断T次,(1<=x<=1e6,t<=1e6)
时间复杂度O( x \sqrt{x} x *T),时间也会比较慢,很容易超时
由此,我们请出今天的主角——素数筛

埃氏筛法

查询的次数较大时,我们通常采用数组预处理存储状态的方法来防止时间过大,这样预处理后,每次查询可以直接得出答案,不必重新判断。
用一个数组Prime[]来存储是否是素数,是素数标记为0,不是则标记为1。并且与上面不同的是,我们在程序开始前对此数组进行预处理。
首先对把2作为因数,累加上去把4,6,8…这些以2为因数的数为下标的值变为1,再是以3作为因数6,9,12,变为1。最后以x-1为因数,至此,所有合数都已经被设为1.
代码

int Prime[(int)1e6+5];
void pre_prime(int x)
{
	Prime[1]=1;
	for(int i=2;i<x;i++)
	{
	if(Prime[i]==0)
		for(int j=i+i;j<=x;j+=i)
		Prime[j]=1;
	}
}

我们还可以对其进行优化,我们发现在遍历的过程中有些是重复的,如i=3时,我们把6,9,12变为1,但在之前6已经被2遍历,Prime[6]已经是1了。
按照我们之前的写法,是对于一个数来说他的因数总是成对出现的,所有合数都会被遍历两次,如在处理因数i的时候(i-1)* i,在之前肯定被i-1的因数遍历了,但如果我们把j的初始值设为i *i,那么就可以避免这个重复。

int Prime[(int)1e6+5];
void pre_prime(int x)
{
	Prime[1]=1;
	for(int i=2;i<x;i++)
	{
	if(Prime[i]==0)
		for(int j=i*i;j<=x;j+=i)
		Prime[j]=1;
	}
}

然后我们可以再对其做出一个小优化,大于sqrt(x)的i,第二个循环一定不进行因为要满足j(i* i)<=x

int Prime[(int)1e6+5];
void pre_prime(int x)
{
	Prime[1]=1;
	int end=sqrt(x);
	for(int i=2;i<=end;i++)
	{
	if(Prime[i]==0)
		for(int j=i*i;j<=x;j+=i)
		Prime[j]=1;
	}
}

下面给出埃氏筛法的gif演示(图片源于网络)
在这里插入图片描述
但我们可以发现,在这之中还是有重复的遍历,如120被2、3、5重复遍历
接下来介绍更加神奇的欧拉筛法

欧拉筛法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值