素数筛
用于快速求出区间
原理
求素数的核心思路均建立在“合数必有质因子”这一性质之上,即通过已知的质数去标记其倍数为合数。两种筛法的核心区别在于标记合数的效率与是否存在重复标记。
1. 埃氏筛
埃氏筛的思想非常直观:从
核心步骤与优化:
- 初始化布尔数组
isPrime,默认所有数均视为质数。 - 从
开始遍历至 : - 若
isPrime[i]为true,说明为质数。 - 从
开始,以步长 递增遍历(即 ),将 isPrime[j]标记为false。
- 若
- 优化点解释:为什么从
开始标记?因为小于 的倍数(如 )已经在之前遍历更小的质数(如 )时被提前标记过了。
缺陷分析:
埃氏筛存在重复标记的问题。例如,
2. 欧拉筛(Sieve of Euler / 线性筛)
欧拉筛的核心目标是解决埃氏筛的重复标记问题,做到每一个合数只被其“最小质因子”筛掉一次,从而达到
核心逻辑与 break 条件:
- 维护一个标记数组
isPrime和一个存储已找到质数的列表primes。 - 从
到 依次遍历: - 若
isPrime[i]为true,将加入 primes列表。 - 遍历已知质数列表中的质数
: - 将
标记为合数。 - 关键退出条件:若
i % p == 0,立即break终止当前内层循环。
- 将
- 若
(为什么 i % p == 0 必须 break)?:
- 当
i % p == 0时,说明是 的最小质因子。 - 如果继续使用下一个质数
去标记 ,则该数的最小质因子实际上是 而不是 。 - 因此,
应该在后续遍历到 时,由质数 负责筛除。 - 通过这个终止机制,确保了每个合数恰好只被其最小质因子访问并标记了一次。
代码实现
模板
cpp
//埃氏筛
std::vector<int> eratosthenesSieve(int n) {
std::vector<bool> isPrime(n + 1, true);
std::vector<int> primes;
if (n < 2) return primes;
isPrime[0] = isPrime[1] = false;
for (int i = 2; i * i <= n; ++i) {
if (isPrime[i]) {
// 从 i * i 开始标记,避免重复标记小于 i^2 的倍数
for (int j = i * i; j <= n; j += i) {
isPrime[j] = false;
}
}
}
for (int i = 2; i <= n; ++i) {
if (isPrime[i]) {
primes.push_back(i);
}
}
return primes;
}复杂度分析
| 名称 | 时间复杂度 | 空间复杂度 | 优缺点与特性 |
|---|---|---|---|
| 埃氏筛 | 优点:逻辑直观,代码极简,常数极小。 缺点:存在多次重复标记合数的情况。 | ||
| 欧拉筛 | 优点:严格线性复杂度,适合顺便预处理积性函数。 缺点:需要额外开辟存储质数的数组,常数略大于埃氏筛。 |