线性筛(Linear Sieve)是一种高效的筛选素数的算法,它相对于埃拉托色尼筛法(埃氏筛)在时间复杂度上有显著优化。埃氏筛的时间复杂度是 O(nloglogn)O(n \log \log n),而线性筛将其优化到了 线性时间复杂度 O(n)O(n)。
一、基本思想
线性筛的核心思想是:每个合数只被它的最小质因数(最小的那个质数因子)筛掉一次。
对比埃氏筛
-
埃氏筛中每找到一个质数 pp,就用它去筛掉所有 p×kp \times k 的数(其中 k≥pk \ge p)。
-
线性筛中每找到一个质数 pp,只用它去筛掉所有合数 p×kp \times k,其中 kk 是当前已经处理过的数,并且保证 p≤kp \le k 且 pp 是 kk 的最小质因数。
二、线性筛的实现
代码实现
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e6 + 10;
vector<int> primes; // 存储所有质数
bool is_composite[MAXN]; // 标记合数
void linear_sieve(int n) {
for (int i = 2; i <= n; ++i) {
if (!is_composite[i]) {
primes.push_back(i); // i 是质数
}
for (int p : primes) {
if (p * i > n) break;
is_composite[p * i] = true;
if (i % p == 0) break; // 保证每个数只被其最小质因数筛去
}
}
}
int main() {
int n = 100;
linear_sieve(n);
for (int p : primes) {
cout << p << " ";
}
cout << endl;
return 0;
}
三、核心原理讲解
对于每个整数 ii,我们要用所有比它小的质数 pp 来生成新的合数 i×pi \times p,这要满足两个条件:
-
p 是质数(由
primes
中维护)。 -
p 是 i 的最小质因数(也就是说,一旦 i 被 p 整除,就不能再被更大的质数继续乘下去生成合数)。
这样可以保证:
-
每个合数最多被筛一次。
-
每个质数只会筛掉有限次合数。
举个例子:
假设我们当前处理到 i=6i = 6,之前筛到的质数有 2,3,52, 3, 5。
-
先尝试乘以 2:6 * 2 = 12 → 标记为合数
-
然后乘以 3:6 * 3 = 18 → 标记为合数
-
然后乘以 5:6 * 5 = 30 → 标记为合数
但注意到当 i=6i = 6 被 2 整除时,我们就不允许之后再用更大的质数去生成合数(比如 6 × 3),以避免重复筛选。
四、时间复杂度分析
每个合数 nn 只会被它的最小质因数 pp 筛掉一次,因此总共不会超过 nn 次筛选。
所以总时间复杂度是 O(n)。
五、拓展用法
-
计算欧拉函数(φ):可以在线性筛的同时计算每个数的欧拉函数值。
-
最小质因数:记录每个合数的最小质因数(可用于质因数分解)。
-
莫比乌斯函数(μ):也可以在线性筛过程中一并求出。