素数筛
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,此时到了其最小质因数处,筛除停止。
从上面的例子很容易看出该思路的正确性,因此线性筛也得以存在。