Saat kita perlu mengetahui apakah setiap bilangan di rentang [1, N] prima atau tidak (misalnya untuk memproses banyak query), memanggil primality test satu per satu membutuhkan waktu O(N · √N) — terlalu lambat. Sieve of Eratosthenes menyelesaikan masalah ini dengan menandai bilangan komposit secara bulk, mencapai O(N log log N). Chapter ini membahas implementasi klasik, optimasi memori dengan bitset, lalu dua varian penting: linear sieve (O(N)) dan segmented sieve (untuk N sangat besar).
Ide Dasar Sieve #
Aturan #
Untuk menemukan semua prima ≤ N:
- Buat array
is_composite[0..N]semulafalse. - Tandai
is_composite[0] = is_composite[1] = true(0 dan 1 bukan prima). - Untuk setiap
p = 2, 3, 4, ...hinggap² ≤ N:- Bila
pbelum ditandai (prima), tandai semua kelipatanp², 2p, 3p, ...sebagai komposit.
- Bila
- Bilangan yang tidak ditandai adalah prima.
Mekanisme: Mengapa Mulai dari p²? #
Saat kita memproses prima p, kelipatan 2p, 3p, ..., (p-1)·p sudah ditandai sebelumnya oleh prima lebih kecil (yaitu prima pembagi 2, 3, ..., p-1). Kelipatan pertama yang belum ditandai adalah p · p = p². Inilah alasan loop batin dimulai dari p² — menghindari pekerjaan redundan dan menjaga kompleksitas O(N log log N).
Implementasi Standar #
Kode: Sieve of Eratosthenes #
#include <iostream>
#include <vector>
// Sieve of Eratosthenes: kembalikan daftar prima <= n
std::vector<int> sieve(int n) {
if (n < 2) return {};
std::vector<bool> is_composite(n + 1, false);
is_composite[0] = is_composite[1] = true;
for (int p = 2; (long long)p * p <= n; ++p) {
if (!is_composite[p]) {
// Mulai dari p*p: kelipatan lebih kecil sudah ditandai oleh prima lebih awal
for (int m = p * p; m <= n; m += p) {
is_composite[m] = true;
}
}
}
std::vector<int> primes;
for (int i = 2; i <= n; ++i) {
if (!is_composite[i]) primes.push_back(i);
}
return primes;
}
int main() {
int n = 50;
auto primes = sieve(n);
std::cout << "Jumlah prima <= " << n << ": " << primes.size() << '\n';
std::cout << "Daftar: ";
for (int p : primes) std::cout << p << ' ';
std::cout << '\n';
// Verifikasi: pi(10^6) = 78498 (known)
auto big = sieve(1'000'000);
std::cout << "pi(10^6) = " << big.size() << " (expected 78498)\n";
// Demo iterasi: untuk n = 30, tampilkan status marking setelah setiap prima
int demo_n = 30;
std::vector<bool> comp(demo_n + 1, false);
comp[0] = comp[1] = true;
for (int p = 2; p * p <= demo_n; ++p) {
if (!comp[p]) {
for (int m = p * p; m <= demo_n; m += p) comp[m] = true;
std::cout << "Setelah mark kelipatan " << p << ": ";
for (int i = 2; i <= demo_n; ++i) {
if (comp[i]) std::cout << "X ";
else std::cout << i << ' ';
}
std::cout << '\n';
}
}
return 0;
}Output yang Dihasilkan:
Jumlah prima <= 50: 15
Daftar: 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
pi(10^6) = 78498 (expected 78498)
Setelah mark kelipatan 2: 2 3 X 5 X 7 X 9 X 11 X 13 X 15 X 17 X 19 X 21 X 23 X 25 X 27 X 29 X
Setelah mark kelipatan 3: 2 3 X 5 X 7 X X X 11 X 13 X X X 17 X 19 X X X 23 X 25 X X X 29 X
Setelah mark kelipatan 5: 2 3 X 5 X 7 X X X 11 X 13 X X X 17 X 19 X X X 23 X X X X X 29 X - Baris
std::vector<bool> is_composite(n + 1, false);: C++ mengkhususkanvector<bool>menjadi packed bit-array (1 bit per elemen) — hemat memori ~8× dibandingvector<char>. UntukN = 10⁸, memori turun dari 100 MB menjadi 12.5 MB. - Baris
for (int p = 2; (long long)p * p <= n; ++p): castpkelong longsebelum perkalianp * pagar tidak overflow saatpmendekati√INT_MAX. Tanpa cast,46341 * 46341overflowint. - Baris
if (!is_composite[p]): lewatipyang sudah ditandai —pbukan prima sehingga kelipatannya sudah ditandai oleh prima pembaginya. - Baris
for (int m = p * p; m <= n; m += p): tandaip², p²+p, p²+2p, ...sebagai komposit. Mulai darip²(bukan2p) — lihat mekanisme di atas. - Hasil
pi(10^6) = 78498: konstanta terkenal dari prime counting function. Implementasi benar. - Demo iterasi: setelah memproses prima
p = 2, semua genap menjadiX. Setelahp = 3, semua kelipatan 3 (yang belumX) ikut ditandai. Setelahp = 5, hanya25yang baru ditandai (karena10, 15, 20sudahXdari kelipatan 2). Bilangan tersisa yang tidakXadalah prima.
Optimasi Memori dengan std::bitset #
Aturan #
std::bitset<N> adalah array bit statis ukuran compile-time. Lebih cepat dari vector<bool> karena ukuran tetap memungkinkan compiler mengoptimalkan akses bit. Untuk N = 10⁷, bitset<10'000'000> hanya butuh ~1.25 MB.
Linear Sieve: O(N) #
Aturan #
Setiap bilangan komposit c ditandai tepat satu kali oleh faktor primanya yang terkecil. Inilah trik linear sieve — kompleksitas turun dari O(N log log N) menjadi O(N).
Mekanisme: Penandaan Minimal Prime Factor #
Untuk setiap i = 2, 3, ..., N:
- Bila
ibelum ditandai, masukkan ke daftar prima. - Untuk setiap prima
pdi daftar (dari kecil ke besar):- Bila
p > N / i, break (i · p akan > N). - Tandai
i · psebagai komposit dengansmallest_prime[i·p] = p. - Bila
i % p == 0, break ( agari · pditandai olehp, bukan prima yang lebih besar — inilah yang menjamin setiap komposit ditandai tepat sekali ).
- Bila
Kode: Linear Sieve #
#include <iostream>
#include <vector>
int main() {
const int N = 30;
std::vector<int> primes;
std::vector<bool> comp(N + 1, false);
for (int i = 2; i <= N; ++i) {
if (!comp[i]) primes.push_back(i);
for (int p : primes) {
long long m = (long long)i * p;
if (m > N) break;
comp[m] = true;
if (i % p == 0) break; // kunci linearitas
}
}
std::cout << "Prima <= " << N << " (" << primes.size() << "): ";
for (int p : primes) std::cout << p << ' ';
std::cout << '\n';
return 0;
}Output yang Dihasilkan:
Prima <= 30 (10): 2 3 5 7 11 13 17 19 23 29 - Baris
if (!comp[i]) primes.push_back(i);:ibelum ditandai oleh prima manapun → prima. - Baris
for (int p : primes): iterasi prima dari kecil ke besar. - Baris
if (m > N) break;:i · pmelebihiN— tidak ada gunanya menandai. - Baris
comp[m] = true;: tandaii · psebagai komposit. - Baris
if (i % p == 0) break;: kunci linearitas. Bilapmembagii, makai · p'untuk primap' > pakan ditandai nanti olehi' = i · p / p' = (i/p) · p'(yang memiliki faktor prima terkecilp'), bukanp. Setiap komposit ditandai tepat sekali oleh faktor primanya yang terkecil.
Segmented Sieve untuk N Besar #
Aturan #
Saat N terlalu besar untuk array O(N) (misal N = 10¹² tetapi kita hanya butuh prima di [L, R] dengan R - L ≤ 10⁷), gunakan segmented sieve:
- Sieve prima hingga
√Rdengan sieve klasik (kecil). - Buat array
block[R-L+1]bertanda “belum komposit”. - Untuk setiap prima
p ≤ √R, hitungstart = max(p², ⌈L/p⌉ · p), lalu tandai kelipatanstart, start+p, ... ≤ Rsebagai komposit.
Kompleksitas dan Perbandingan #
| Varianta | Waktu | Memori | Kapan dipakai |
|---|---|---|---|
| Sieve klasik | O(N log log N) | O(N) bit | Default untuk N ≤ 10⁸ |
| Sieve + bitset | O(N log log N) | O(N) bit (const) | N tetap saat compile-time |
| Linear sieve | O(N) | O(N) byte | Perlu smallest prime factor (SPF) |
| Segmented sieve | O((R-L+1) log log R + √R) | O(R-L+1 + √R) | Rentang sempit di N besar |
Aplikasi #
- Primality precheck: bangun sieve
≤ √N_maxsekali, lalu primality test cepat bagin ≤ N_max. - Prime counting function
π(N): hitung berapa banyak prima≤ N. - Faktorisasi cepat: setelah sieve linear menyimpan
smallest_prime[i], faktorisasinadalahn → n/sp[n] → ... → 1dalamO(log n). Lihat Chapter Faktorisasi Prima . - Möbius function, Euler’s totient via sieve linear — bisa dihitung bersamaan dalam
O(N).
Ringkasan #
- Sieve klasik: tandai kelipatan
p², 2p, ...untuk setiap primap ≤ √N. KompleksitasO(N log log N), memoriO(N)bit. - Mulai marking dari
p²(bukan2p) — kelipatan2p, ..., (p-1)psudah ditandai oleh prima lebih kecil. std::vector<bool>danstd::bitsetmemakai 1 bit per elemen — hemat memori.- Linear sieve:
O(N)dengan break padai % p == 0— setiap komposit ditandai tepat sekali. - Segmented sieve: untuk
[L, R]diNbesar, gunakan sieve≤ √Rlalu tandai di blok[L, R].