筛法求素数
首先我们知道任何一个合数都可以由素数的乘积得到,如果我们将得到的素数进行乘积运算标记合数那么就会节省很多时间。而标记的这一环节就是筛法名称的由来。
#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下标 | 0 | 1 | 2 | 3 | … |
---|---|---|---|---|---|
乘数\质数 | 2 | 3 | 5 | 7 | … |
2 | 2x2 | 2x3 | 2x5 | 2x7 | … |
3 | 3x2 | 3x3 | 3x5 | 3x7 | … |
4 | 4x2 | 4x3 | 4x5 | 4x7 | … |
5 | 5x2 | 5x3 | 5x5 | 5x7 | … |
6 | 6x2 | 6x3 | 6x5 | 6x7 | … |
7 | 7x2 | 7x3 | 7x5 | 7x7 | … |
… | … | … | … | … | … |
我们发现 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下标 | 0 | 1 | 2 | 3 | … |
---|---|---|---|---|---|
乘数\质数 | 2 | 3 | 5 | 7 | … |
2 | 2x2 | … | |||
3 | 3x2 | 3x3 | |||
4 | 4x2 | 4x3 | … | ||
5 | 5x2 | 5x3 | 5x5 | … | |
6 | 6x2 | 6x3 | 6x5 | … | |
7 | 7x2 | 7x3 | 7x5 | 7x7 | … |
… | … | … | … | … | … |
我们可以看到已经省去了很多运算。但是我们仍可以看出,这里面有重复的运算,例如 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下标 | 0 | 1 | 2 | 3 | … |
---|---|---|---|---|---|
乘数\质数 | 2 | 3 | 5 | 7 | … |
2 | 2x2 | … | |||
3 | 3x2 | 3x3 | |||
4 | 4x2 | … | |||
5 | 5x2 | 5x3 | 5x5 | … | |
6 | 6x2 | … | |||
7 | 7x2 | 7x3 | 7x5 | 7x7 | … |
… | … | … | … | … | … |
又简便了许多。
萌新第一次写题解,若有不对请及时指正,谢谢。