筛质数的问题是给定一个整数N,统计或求出1-N之间所有的质数,称为质数的筛选问题
一.埃式筛
埃式筛的原理是任意一个整数的倍数都是合数。那么我们扫描每个数,将他所有小于N的倍数标记为合数。这样一直扫描,一旦扫描到的数没有被标记则这个数必然没有其他因子,这个数就为质数。
模拟过程如下:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,...
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,...
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,...
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,...
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,...
加粗的地方表示遍历到这个点,如果没有被标记则为质数。红色为标记。
关于埃氏筛的优化:
我们可以看到在上面的模拟过程中 先是2将他的所有倍数标记(4,6,8,10,12)但是当进行到3记3的倍数的时候我们会惊奇的发现,3同样会标记6与12,这样意味 2与3进行了一些重复标记。
我们可以发现当扫描到i的时候,小于i²的i的倍数必然在之前就被比i小的数扫描过了。因为,i前面的数在乘2,乘3这样倍增的时候必然乘过i,则i必然不是小于i²的i的倍数的最小因子,便会出现重复标记,我们可以直接从i²后面的i的倍数进行标记。这就是埃氏筛的优化
代码实现(这里只提供优化后的版本):
const int N =1e6+10;
int st[N]; //判断i是否被删除过(是否为合数),是合数st[i]为1
int h[N],tt; //h[N]装所有小于n的质数,tt指向末尾元素加一
int n; //给定一个整数n,求1-n一共有多少个质数
void get_primes(int n)
{
for(int i=2;i<=n;i++) //从2到n遍历整个序列(注意1不是质数)
{
if(st[i]) continue; //如果被标记,则直接进行下一次遍历
h[tt++]=i;
for(int j=i;j<=n/i;j++) st[j*i]=1; //写成j<=n/i而不是j*i<=n主要是怕j*i会爆int.
//将所有i的j次倍都标记为合数 //tt为序列中一共有多少个质数
}
}
时间复杂度:
O(NloglogN) 基本就是N后面添了一个常数,接近于线性
二.欧式筛(又称线性筛法,因为他的复杂度就是线性而不是接近)
在上面的埃氏筛中,2从4开始标记合数,3从9开始标记合数。我们会发现虽然免去了重复标记6,但最后这两个数还是免不了重复标记一个数:12 。究其根本原因是2是12的质因子但同样3也是12的质因子,那么有没有一种方法让每个数都被他的最小质因子标记。这就是接下来的欧式筛。
思路就是将所有质数放入一个数组里。i从2到遍历,将i与每个小于i的质数相乘筛去他们的乘积,直到i等于那个质数或那个质数是i的质因子的时候停下,这样子必然满足合数是由最小质因子筛去的(因为每个合数其实都可以看成一个很小的质数和一个数相乘得到)
代码实现:
const int N =1e6+10;
int st[N]; //判断i是否被删除过(是否为合数),是合数st[i]为1
int h[N],tt; //h[N]装所有小于n的质数,tt指向末尾元素加一
int n;
void get_primes(int n)
{
for(int i=2;i<=n;i++) //线式筛是遍历n中所有元素,每次都让n乘上所有小于他的质数,来筛去合数
{
if(!st[i]) h[tt++]=i; //只要不为合数则入队
for(int j=0;h[j]<=n/i;j++)
{
st[h[j]*i]=1;
if(i%h[j]==0) break; //当i==h[j]或者h[j]是i的最小质因子的时候结束当前循环,进行下一个i
}
}
}