Sieve of Eratosthenes & Linear Sieve
埃拉托斯特尼篩法是一種用來生成質數的篩法,得名於古希臘數學家 埃拉托斯特尼。
— 維基百科
簡單說:一個古老的 質數篩選演算法,用來找出某個範圍內所有的質數。
質數是指在大於 1 的自然數中,除了 1 和該數自身外,無法被其他自然數整除的數。
— 維基百科
| 數字 | 因數 | 是否為質數 |
|---|---|---|
| 7 | 1, 7 | ✓ 是 |
| 12 | 1, 2, 3, 4, 6, 12 | ✗ 否(合數) |
| 1 | 1 | 既非質數也非合數 |
直覺上,判斷 n 是否為質數可以這樣寫:
bool is_prime(int n){
if(n < 2) return false;
for(int i = 2 ; i * i <= n ; ++i){
if(n % i == 0)
return false;
}
return true;
}
i * i <= n 而非 i <= sqrt(n)時間複雜度 \(O(n \log \log n)\),透過逐步篩除倍數找出所有質數
對於範圍 \([2, n]\) 內的每一個合數 \(c\),它必定會被某個 \(\leq \sqrt{n}\) 的質數篩掉。
若 \(c\) 是合數,則 \(c\) 的最小質因數 \(p\) 滿足 \(p \leq \sqrt{c}\)
證明(反證法):假設合數 \(c\) 的所有質因數都 \(> \sqrt{c}\),那麼至少有兩個質因數 \(r, k\):
篩法只需對 \([2, \sqrt{n}]\) 內的質數執行篩除,即可找出 \([2, n]\) 內所有質數。
證明:取 \([2, n]\) 內任意合數 \(c\)
#include <iostream>
#include <vector>
using namespace std;
const int MAXN = 1e7 + 5;
bool is_composite[MAXN]; // true = 合數,false = 質數
vector<int> primes;
void sieve(int n) {
is_composite[0] = is_composite[1] = true;
for (int i = 2; i * i <= n; ++i) {
if (!is_composite[i]) {
// 從 i*i 開始(更小的倍數已被更小質數標記)
for (int j = i * i; j <= n; j += i)
is_composite[j] = true;
}
}
for (int i = 2; i <= n; ++i)
if (!is_composite[i])
primes.push_back(i);
}
i*i 開始篩,比 i*i 小的 \(i\) 的倍數已被更小質數標記,不需重複處理
時間複雜度 \(O(n)\),每個合數恰好只被篩一次,是埃篩的進化版本
埃篩的核心缺陷:合數會被重複標記
primes = []
i = 2:未被標記 → primes = [2]
j=2: 標記 2×2=4, 2 % 2 == 0 → break
i = 3:未被標記 → primes = [2, 3]
j=2: 標記 3×2=6, 3 % 2 != 0 → 繼續
j=3: 標記 3×3=9, 3 % 3 == 0 → break
i = 4:已被標記 → 不加入 primes
j=2: 標記 4×2=8, 4 % 2 == 0 → break ← 非質數也要跑!
i = 5:未被標記 → primes = [2, 3, 5]
j=2: 標記 5×2=10, 5 % 2 != 0 → 繼續
j=3: 標記 5×3=15, 5 % 3 != 0 → 繼續
j=5: 標記 5×5=25, 5 % 5 == 0 → break
i = 6:已被標記 → 不加入 primes
j=2: 標記 6×2=12, 6 % 2 == 0 → break
i % prime == 0 要 break?假設 i = 6,若不 break,想標記 6 × 3 = 18:
當 i % prime == 0 時,設 \(i = prime \times k\),下一個想標記的合數:
因為質數遞增,\(prime < next_{prime}\),所以 \(prime\) 才是 \(r\) 真正的最小質因數。 \(r\) 應在未來 \(i' = k \times next_{prime}\) 時被篩掉 → 現在必須 break。
每個合數 \(c\) 都有唯一的最小質因數 \(p\),可以寫成:
primes 裡,且不會 break
提前結束,所以 \(c\) 只會被標記恰好一次 ✓
| 演算法 | 時間複雜度 | 每個合數標記次數 |
|---|---|---|
| 埃拉托斯特尼篩法 | \(O(n \log \log n)\) | 多次(重複) |
| 線性篩(歐拉篩) | \(O(n)\) | 恰好一次 |
const int MAXN = 1e7 + 5;
bool is_composite[MAXN];
vector<int> primes;
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 ((long long)i * p > n) break; // 超出範圍
is_composite[i * p] = true; // 用最小質因數標記
if (i % p == 0) break; // ← 核心:確保只篩一次
}
}
}
(long long)i * p:當 i 和 p 都很大時,相乘可能造成 int 溢位
下圖是我看到莫比烏斯函數時的表情:
圖片來源:《蒼之彼方的四重奏》
絕對不是因為我看它有點複雜,不想研究