埃氏筛法的更优化——欧拉筛法的详解

本文介绍欧拉筛法的原理和代码实现,对比埃氏筛法,欧拉筛法在大规模数据下效率更高,避免重复标记素数的倍数。通过解析关键代码段,阐述如何通过线性复杂度实现素数筛选,并强调了素数倍数只被最小素数因子标记一次的优化策略。

这个线性复杂度的欧拉素数筛法,爱了爱了

今天讲一下关于欧拉筛法的原理和代码实现,实不相瞒,我也才刚get到这个筛法的点,乘着记忆清晰来教一遍梳理一下思路。

我查阅资料的时候也在很多博客和公众号上看到关于欧拉筛法的解释和代码实现,然后想学习了之后用我自己的话重新描述一遍,希望我的角度能让你有所收获!

欧拉筛法与埃氏筛法一样,都是围绕【素数的倍数不是素数】这个核心原理来展开的,有关埃氏筛法请移步我的上一篇博客“埃氏筛法的详解”,但是它比埃氏筛法更高效,当数据量足够大的时候,欧拉筛法的时间是埃氏筛法时间的十分之一甚至更少

现在我们正式展开欧拉筛法的学习。

欧拉筛法效率高的一个重要原因是,它不会重复标记一个数是不是素数的倍数,例如:6是素数2的倍数,同时也是素数3的倍数,欧拉算法能做到只标记一次6。

我们先看一下代码

#include <stdio.h>
#include <string.h>
using namespace std;

int main()
{
   
   
	int n, cnt = 0; // n是你要找的素数范围,cnt代表在这个范围内素数的个数

    int prime[100001]
欧拉筛法(线性筛)埃氏筛法拉托斯特尼筛法)都是用于**效筛选出小于等于某个整数 n 的所有素数**的算法。它们在时间复杂度、实现方式适用场景上有显著区别。 下面我将详细解释两者的原理、C++ 实现,并对比其优劣。 --- ## ✅ 1. 拉托斯特尼筛法(Sieve of Eratosthenes,简称“筛”) ### 🔍 原理: - 从最小的质数 2 开始,把它的所有倍数(除了自己)标记为合数。 - 然后找下一个未被标记的数,它一定是质数,再将其所有倍数标记为合数。 - 重复此过程直到遍历到 √n。 > ❗ 注意:一个合数可能被多个质因数重复标记(如 12 被 2 3 都标记过),这是效率损失的原因。 ### ⏱ 时间复杂度:O(n log log n) ### ✅ 空间复杂度:O(n) ### 📌 C++ 实现: ```cpp #include <iostream> #include <vector> using namespace std; vector<int> sieve_of_eratosthenes(int n) { vector<bool> is_prime(n + 1, true); vector<int> primes; is_prime[0] = is_prime[1] = false; for (int i = 2; i * i <= n; i++) { if (is_prime[i]) { // 标记 i 的所有倍数为非质数 for (int j = i * i; j <= n; j += i) { is_prime[j] = false; } } } // 收集所有质数 for (int i = 2; i <= n; i++) { if (is_prime[i]) { primes.push_back(i); } } return primes; } int main() { int n = 30; vector<int> primes = sieve_of_eratosthenes(n); cout << "Primes up to " << n << ": "; for (int p : primes) { cout << p << " "; } cout << endl; return 0; } ``` --- ## ✅ 2. 欧拉筛法(Euler's Sieve,又称线性筛) ### 🔍 原理: - 维护一个 `primes` 数组记录已知质数。 - 对每个数 i(从 2 到 n),用已知质数去筛掉 `i × primes[j]`。 - **关键优化**:一旦 `primes[j]` 整除 `i`,就停止后续筛除 —— 这保证了每个合数只被它的**最小质因数**筛一次。 > ✅ 因此,每个合数仅被标记一次 → 时间复杂度 O(n) ### ⏱ 时间复杂度:O(n)(线性) ### ✅ 空间复杂度:O(n) ### 📌 C++ 实现: ```cpp #include <iostream> #include <vector> using namespace std; vector<int> euler_sieve(int n) { vector<bool> is_prime(n + 1, true); vector<int> primes; is_prime[0] = is_prime[1] = false; for (int i = 2; i <= n; i++) { if (is_prime[i]) { primes.push_back(i); } // 用已知质数去筛合数 for (int j = 0; j < primes.size() && i * primes[j] <= n; j++) { is_prime[i * primes[j]] = false; if (i % primes[j] == 0) { break; // 关键:primes[j] 是 i 的最小质因数 } } } return primes; } int main() { int n = 30; vector<int> primes = euler_sieve(n); cout << "Primes up to " << n << ": "; for (int p : primes) { cout << p << " "; } cout << endl; return 0; } ``` --- ## 🔁 两种筛法的核心区别 | 特性 | 筛 | 欧拉筛 | |------|--------|--------| | 时间复杂度 | O(n log log n) | O(n)(线性) | | 是否重复标记 | 是(如 12 被 2 3 都筛) | 否(每个合数只被最小质因数筛) | | 实现难度 | 简单直观 | 稍复杂,需理解 `i % primes[j] == 0` 的意义 | | 筛的过程中是否收集质数 | 可以 | 必须收集 | | 适合学习阶段 | 初学者 | 中级/进阶 | | 扩展性(如求最小质因数、φ 函数等) | 差 | 强 | --- ## 💡 为什么 `i % primes[j] == 0` 就要 break? 假设 `i` 能被 `primes[j]` 整除,说明 `primes[j]` 是 `i` 的一个质因数。 那么对于下一个质数 `primes[j+1] > primes[j]`,考虑 `i * primes[j+1]`: - 它的最小质因数其实是 `primes[j]`(因为 `i` 包含 `primes[j]`) - 所以应该由 `primes[j]` 来筛掉它,而不是现在就筛 👉 提前 break 可避免重复筛,确保线性复杂度。 --- ## ✅ 示例输出(n=30): ``` Primes up to 30: 2 3 5 7 11 13 17 19 23 29 ``` 两者输出一致,但欧拉效。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值