在学习筛质数前,首先要了解什么是质数?简单来说质数就是只能被1和数本身整除的数。
那我们如何来求出1~n中的质数呢(1<=n<=1000000)。
最简单的方法那就是两重循环来判断1~n中的每个数是不是质数。外循环n次,内循环
n 次 时间复杂度是O(n*sqrt(n)) 。
为了优化代码我们可以引入埃式筛法。如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int a[N],primes[N],cnt;
bool st[N];
void deal_primes(int n)
{
for(int i = 2; i <= n; i ++ )
{
if(st[i]) continue; //如果 st[i] == true 说明是质数的倍数,跳过
primes[cnt ++ ] = i; //存入质数
for(int j = i; j <= n; j += i)
st[j] = true; //质数的倍数一定不是质数,true标记下来
}
}
int main()
{
int n;
cin >> n;
deal_primes(n);
for(int i = 0; i < cnt; i ++ ) cout << primes[i] << " ";
}
时间复杂式O(N ln ln N)级别的。
但是我们可以发现在对质数的倍数进行标记的时候,会出现重复的现象。
例如:2的三倍是6,进行一次标记,而3的二倍是6,也进行了一次标记。这样就出现了很多次重复。那我们有没有什么方法可以减少重复甚至不出现重复。
我们引入一个线性筛法。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int a[N],primes[N],cnt;
bool st[N];
void deal_primes(int n)
{
for(int i = 2; i <= n; i ++ )
{
if(!st[i]) primes[cnt ++ ] = i;
for(int j = 0; primes[j] <= n / i; j ++ )
{
st[primes[j] * i] = true;
if(i % primes[j] == 0) break;
}
}
}
int main()
{
int n;
cin >> n;
deal_primes(n);
for(int i = 0; i < cnt; i ++ ) cout << primes[i] << " ";
}
我们来解释一下代码。
对于一个数 i 和一个质数primes[j],primes[j] 一定是 i * primes[j] 的一个质因数。
又因为j是从0开始遍历,所以当 i % primes[j] 时,我们可以保证primes[j] 一定是 i 的一个最小的质因数。而每一个合数我们都用他的最小质因数来计算。
那么我们就可以保证1~n中所以的数只被处理过一次。
对于1000000的数可以比埃式筛法快13ms。