素数筛

素数筛

 2021-01-27  20:56:51

    最近在学习的过程中,又一次看到了关于素数筛的问题,就想来过来做一个总结。


 

1.普通求素数

    这边直接上代码,很好理解,不多解释:

#include<cstdio>
#include<cstring>
#include<cmath>
#define N 1000000
using namespace std;
int main()
{
	int t;
	printf("2\n3\n");
	for(int i=4;i<=N;i++)
	{
		t=1;
		for(int j=2;j<=sqrt(i);j++)
		{
			if(i%j==0)
			{
				t=0;
				break;
			}
		}
		if(t==0)
		continue;
		printf("%d\n",i);
	}
}

2.普通素数筛

    我们每得到一个素数,就把它的2倍,3倍...n倍给直接筛除掉,这样就可以去减少运行时间。代码如下:

#include<cstdio>
#include<cstring>
#define N 10000
bool prime[N];
using namespace std;
int main()
{
	memset(prime,1,sizeof(prime));
	for(int i=2;i<=N;i++)
	{
		if(prime[i]==1)
		{
			printf("%d\n",i);
			for(int j=i*i/*此处使用i^2而非2*i可以进一步增加运行速度*/;j<=N;j=j+i)
			{
				prime[j]=0;
			}
		}
	}
	return 0;
}

  但是,这仍非最快的筛法,当数据较大时,仍需要较大的时间。

    致使该筛法速度不够快的因素主要是:有部分数字会被重复筛除。例如100这个数字,既会被2筛除一次,又会被5筛除(注释出用i^2而非2*i也考虑了这个因素),因此若想继续优化程序,就要从这个方面想办法。


 

3.线性筛(欧拉筛)

    从名字可以看出,这种方法的时间是线性的,即其时间复杂度为O(n)。该算法保证了每一个数字只被一个数(可能是合数也可能是素数)筛除而不会重复。

代码如下:

#include<cstdio>
#include<cstring>
constexpr auto N = 100000;//0~N
bool prime[1000000];//若为质数值为1,否则为0
int num[1000000];//存质数
using namespace std;
int main()
{
	int count=0;//用来存以判断的质数的个数
	memset(prime, 1 , sizeof(prime));
	for (int i = 2; i < N; i++)
	{
		if (prime[i] == 1)
		{
			num[count++] = i;
			printf("%d\n", i);
		}
		for (int j = 0; j < count && num[j] * i<= N; j++)
		{
			prime[num[j] * i] = 0;
			if (i % num[j] == 0)//直到num[j]为i的最小质因数
			{
				break;
			}
		}
	}
}//每一个合数有且仅有一个数去把它筛掉,故时间复杂度为O(n).

  下面来解释一下为什么该算法中每一个合数只会被筛除一次。

    由算数基本定理知,每一个合数都可以分解为有限个素数的乘积。其中,最小的那个质数我们叫做最下质因数。在代码中,num[j] * i即为我们所要筛除的那个合数,i为我们所循环到的数字。每一次j都从0开始,即num[j]从2开始,一直到num[j]为i的最小质因数为止,这样便可以确保每一个合数只是由一个数筛除的。

下面举几个例子

对于一个合数,箭头右面表示筛除它的数字:

15->5    45->15->5    30->15    100->50->25->5   99->33->11

下面跟代码从正面去看下:

一个数i,例如i=15,它的最小质因数是3,从2开始,首先它筛除掉15*2=30,然后15*3=45,此时到了其最小质因数处,筛除停止。

从上面的例子很容易看出该思路的正确性,因此线性筛也得以存在。

 

 

posted @ 2021-01-27 20:54  黯战丿风魂  阅读(99)  评论(0)    收藏  举报