Sepanjang Section ini kita melihat algoritma greedy yang optimal: activity selection, fractional knapsack, Huffman, job scheduling. Namun tidak semua greedy optimal — pembuktian diperlukan sebelum kita percaya bahwa strategi greedy benar-benar menghasilkan solusi global optimal. Pada Chapter ini kita akan membahas tiga teknik formal pembuktian greedy: exchange argument, greedy stays ahead, dan matroid theory. Kita juga akan memverifikasi secara empiris bahwa greedy cocok dengan brute force pada banyak instance acak — validasi numerik terhadap pembuktian analitik.
Aturan: Tiga Teknik Pembuktian Greedy #
Pembuktian optimalitas greedy umumnya menggunakan salah satu dari tiga teknik berikut.
1. Exchange Argument #
Teknik paling umum. Langkahnya:
- Misalkan
S*adalah solusi optimal yang tidak memuat pilihan greedy pertamag. - Identifikasi elemen
ediS*yang “bersaing” dengang. - Ganti
edengangdiS*, menghasilkanS'. - Tunjukkan
S'tetap feasible danS'tidak lebih buruk dariS*. - Karena
S*optimal,S'juga optimal. Maka ada solusi optimal yang memuatg— greedy-choice property terbukti.
2. Greedy Stays Ahead #
Bandingkan greedy dengan solusi optimal langkah demi langkah. Tunjukkan bahwa setelah k langkah, solusi greedy “lebih maju” (mis. jumlah elemen ≥, total profit ≥, finish time ≤) dibanding k elemen pertama solusi optimal. Maka di akhir, greedy minimal sama baik dengan optimal.
3. Matroid Theory #
Kerangka formal. Bila masalah dapat dimodelkan sebagai matroid (E, I) (dengan E himpunan elemen, I himpunan independent set yang memenuhi tiga aksioma), maka greedy selalu optimal. Contoh: MST berbasis graphic matroid; job scheduling berbasis transversal matroid.
Mekanisme: Contoh Exchange Argument pada Activity Selection #
Mari kita bedah exchange argument untuk activity selection secara formal.
Spesifikasi: diberikan n aktivitas dengan (start, finish), pilih himpunan kompatibel (non-overlapping) maksimum.
Greedy: sort by finish ascending, pilih aktivitas dengan finish paling awal, lalu iterasi pilih yang kompatibel.
Pembuktian:
- Misalkan
S* = {a_1, a_2, ..., a_k}solusi optimal dengan aktivitas terurut by finish ascending. Misalkangadalah aktivitas dengan finish paling awal di seluruh input. g.finish ≤ a_1.finish(karenagfinish paling awal).- Aktivitas apa pun yang kompatibel dengan
a_1(start ≥a_1.finish) juga kompatibel dengang(karenag.finish ≤ a_1.finish⟹ start ≥a_1.finish≥g.finish). - Substitusi:
S' = (S* \ {a_1}) ∪ {g}.S'feasible karenagkompatibel dengana_2(starta_2≥a_1.finish≥g.finish). |S'| = |S*| = k, jadiS'juga optimal dan memuatg.- Greedy-choice property terbukti: ada solusi optimal yang memuat
g.
Setelah g dipilih, sub-masalah adalah memilih aktivitas maksimum dari {a : a.start ≥ g.finish} — struktur identik dengan masalah awal. Maka optimal substructure terpenuhi, dan greedy optimal.
Mekanisme: Greedy Stays Ahead pada Huffman Coding #
Untuk membuktikan Huffman optimal, teknik greedy stays ahead cocok digunakan. Intuisi: Huffman selalu menggabungkan dua node dengan frekuensi terkecil — ini menjaga pohon “tetap seimbang dari bawah” sehingga total panjang kode minimum.
Skema bukti (ringkas):
- Misalkan ada pohon prefix code optimal
T*dengan total panjangL*. - Misalkan
xdanyadalah dua simbol dengan frekuensi terkecil. Bilaxdanybukan sibling paling dalam diT*, lakukan rotasi: tukar posisixdengan node paling dalam. Total panjang tidak naik karenafreq(x)minimal — rotasi menghemat atau sama. - Setelah rotasi,
xdanymenjadi sibling paling dalam. Gabungkan keduanya menjadi node baru dengan frekuensifreq(x) + freq(y). - Sub-masalah: bangun Huffman untuk
n-1simbol (simbolx, ydiganti dengan node gabungan). Sub-masalah punya struktur identik. - Induksi pada
n: asumsi greedy optimal untukn-1simbol, maka optimal untuknsimbol.
Pembuktian rinci memerlukan induksi formal, namun intinya: greedy stays ahead karena setiap merge Huffman tidak menambah panjang lebih dari yang diperlukan oleh optimalitas lokal.
Kode: Verifikasi Empiris Greedy vs Brute Force #
Pembuktian formal meyakinkan, tetapi verifikasi empiris dengan random testing memberikan keyakinan tambahan dan membantu mendeteksi bug implementasi. Berikut kode yang menguji kesesuaian greedy dengan brute force pada activity selection.
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
struct Activity {
int start, finish;
};
// Brute force: cek semua subset, kembalikan ukuran maksimum kompatibel
int bruteForce(const std::vector<Activity>& acts) {
int n = acts.size();
int best = 0;
for (int mask = 0; mask < (1 << n); ++mask) {
bool ok = true;
for (int i = 0; i < n && ok; ++i) {
if (!(mask & (1 << i))) continue;
for (int j = i + 1; j < n && ok; ++j) {
if (!(mask & (1 << j))) continue;
// Cek overlap: a dan b overlap bila start < finish_other
if (acts[i].start < acts[j].finish &&
acts[j].start < acts[i].finish) {
ok = false;
}
}
}
if (ok) {
int cnt = __builtin_popcount(mask);
best = std::max(best, cnt);
}
}
return best;
}
// Greedy: sort by finish ascending, pilih kompatibel
int greedy(const std::vector<Activity>& acts) {
std::vector<Activity> sorted = acts;
std::sort(sorted.begin(), sorted.end(),
[](const Activity& a, const Activity& b) {
return a.finish < b.finish;
});
int count = 0, lastFinish = -1;
for (const auto& a : sorted) {
if (a.start >= lastFinish) {
++count;
lastFinish = a.finish;
}
}
return count;
}
int main() {
std::mt19937 rng(42);
std::uniform_int_distribution<int> distN(3, 10);
std::uniform_int_distribution<int> distStart(0, 18);
std::uniform_int_distribution<int> distLen(1, 6);
const int NUM_TESTS = 200;
int passes = 0;
for (int t = 1; t <= NUM_TESTS; ++t) {
int n = distN(rng);
std::vector<Activity> acts(n);
for (auto& a : acts) {
int s = distStart(rng);
int len = distLen(rng);
a.start = s;
a.finish = s + len;
}
int bf = bruteForce(acts);
int gr = greedy(acts);
bool pass = (bf == gr);
if (pass) ++passes;
if (t <= 5 || !pass) {
std::cout << "Tes " << t << " (n=" << n
<< "): brute=" << bf << ", greedy=" << gr
<< " [" << (pass ? "PASS" : "FAIL") << "]\n";
}
}
std::cout << "\nTotal: " << passes << "/" << NUM_TESTS << " tes lulus.\n";
if (passes == NUM_TESTS) {
std::cout << "Greedy == Brute pada semua instance acak — "
"pembuktian analitik terkonfirmasi empiris.\n";
}
return 0;
}Output yang Dihasilkan:
Tes 1 (n=5): brute=4, greedy=4 [PASS]
Tes 2 (n=3): brute=3, greedy=3 [PASS]
Tes 3 (n=8): brute=4, greedy=4 [PASS]
Tes 4 (n=3): brute=2, greedy=2 [PASS]
Tes 5 (n=4): brute=3, greedy=3 [PASS]
Total: 200/200 tes lulus.
Greedy == Brute pada semua instance acak — pembuktian analitik terkonfirmasi empiris.Bedah Kode Baris-per-Baris #
for (int mask = 0; mask < (1 << n); ++mask): iterasi semua subset aktivitas.2^nkemungkinan — hanya feasible untuknkecil (≤ 20).for (int i = 0; i < n && ok; ++i) for (int j = i + 1; j < n && ok; ++j): untuk setiap pasangan aktivitas di subset, cek overlap. KompleksitasO(n^2)per subset.if (acts[i].start < acts[j].finish && acts[j].start < acts[i].finish): kondisi overlap dua interval. Bila kedua kondisi true, interval tumpang tindih.best = std::max(best, __builtin_popcount(mask));:__builtin_popcountmenghitung jumlah bit1(jumlah aktivitas di subset). Catat maksimum.std::sort(sorted.begin(), ..., [](...) { return a.finish < b.finish; }): greedy sort — kunci optimalitas activity selection.if (a.start >= lastFinish) { ++count; lastFinish = a.finish; }: pilih aktivitas yang kompatibel dengan yang terakhir dipilih.std::mt19937 rng(42);: PRNG dengan seed fixed. Seed tetap menjamin output reproducible di seluruh run.std::uniform_int_distribution<int> distN(3, 10);: jumlah aktivitas per test acak di [3, 10]. Batas atas 10 karena brute forceO(2^10 × 10^2) = 102400per test.if (passes == NUM_TESTS): bila semua tes lulus, greedy terbukti konsisten dengan brute force pada sample acak. Ini bukan pembuktian formal — tetapi memberi keyakinan kuat terhadap pembuktian analitik.
Matroid Theory: Kerangka Formal #
Matroid (E, I) terdiri dari himpunan elemen E dan koleksi independent set I ⊆ 2^E yang memenuhi aksioma:
- Kosong independent:
∅ ∈ I. - Heredity: bila
B ∈ IdanA ⊆ B, makaA ∈ I. - Augmentation: bila
A, B ∈ Idengan|A| < |B|, maka adae ∈ B \ AsehinggaA ∪ {e} ∈ I.
Teorema: bila masalah optimasi dapat dimodelkan sebagai matroid berbobot (E, I, w) (dengan w: E → ℝ), maka algoritma greedy (sort by weight descending, tambah elemen selama tetap independent) menghasilkan basis dengan bobot maksimum.
Contoh matroid:
| Matroid | Elemen E | Independent set I | Greedy = |
|---|---|---|---|
| Graphic matroid | Edge graph | Hutan (acyclic edge set) | MST (Kruskal) |
| Linear matroid | Kolom matriks | Subset kolom linear independen | Basis maksimum |
| Partition matroid | Elemen dikelompokkan | Subset dengan batas per grup | Assignment |
| Transversal matroid | Elemen ↔ kelompok | Matching | Job scheduling (varian) |
| Uniform matroid | Elemen | Subset berukuran ≤ k | Top-k selection |
Tabel Pembuktian untuk Algoritma Greedy Klasik #
| Algoritma | Teknik Pembuktian | Catatan |
|---|---|---|
| Activity selection | Exchange argument | Tukar aktivitas pertama dengan earliest finish. |
| Fractional knapsack | Exchange argument | Substitusi parsial barang rasio tinggi. |
| Huffman coding | Greedy stays ahead | Rotasi dua node terkecil ke sibling paling dalam. |
| Job scheduling | Exchange argument | Tukar job profit rendah dengan profit tinggi di slot sama. |
| Dijkstra (non-neg weight) | Greedy stays ahead | Finalisasi node dengan distance minimum. |
| Kruskal MST | Matroid (graphic) | Teorema graphic matroid. |
| Prim MST | Greedy stays ahead / cut property | Edge termasuk cut dengan bobot minimum. |
| Making change (canonical) | Exchange argument + structure teorema | Bergantung pada denominasi (canonical). |
Komparasi Teknik Pembuktian #
| Teknik | Kapan Dipakai | Kelebihan | Kekurangan |
|---|---|---|---|
| Exchange argument | Umum — swap elemen | Intuisi langsung; universal | Bila banyak elemen bertukar, formalisasi rumit |
| Greedy stays ahead | Bila greedy punya order natural | Bersih untuk MST, Huffman | Tidak semua greedy punya order |
| Matroid theory | Bila masalah = matroid | Teorema kuat, langsung pakai | Tidak semua masalah berbentuk matroid |
| Empirical testing | Validasi numerik | Mendeteksi bug implementasi | Bukan pembuktian formal |
Aplikasi Nyata Pembuktian Greedy #
- Verifikasi algoritma kompetisi: setelah implement greedy, uji dengan brute force pada
n ≤ 10untuk memastikan tidak ada counter-example. - Design algoritma baru: pertukaran gagasan antara greedy dan DP — bila greedy terbukti via exchange argument, gunakan greedy; bila tidak, fallback ke DP.
- Sertifikasi sistem kritis: algoritma scheduling pada avionics, medical devices memerlukan pembuktian formal sebelum deploy.
- Optimasi compiler: pembuktian register allocation (graph coloring pada chordal graph, greedy optimal).
Ringkasan #
- Tiga teknik pembuktian greedy: exchange argument, greedy stays ahead, matroid theory.
- Exchange argument: tukar elemen solusi optimal dengan pilihan greedy, tunjukkan tetap optimal.
- Greedy stays ahead: bandingkan greedy dan optimal langkah demi langkah, tunjukkan greedy ≥ optimal.
- Matroid theory: kerangka formal — bila masalah = matroid, greedy dijamin optimal (mis. MST, Kruskal).
- Verifikasi empiris dengan random testing melengkapi pembuktian analitik — mendeteksi bug implementasi.
- Activity selection: bukti via exchange argument (aktivitas dengan finish paling awal selalu bisa dimasukkan ke solusi optimal).
- Huffman: bukti via greedy stays ahead (rotasi dua node terkecil ke sibling paling dalam).
Section Greedy Algorithms berakhir di sini. Bab berikutnya akan masuk ke Section Dynamic Programming — paradigma yang lebih aman tetapi lebih lambat, yang mengeksplorasi semua pilihan dan menyimpan hasil untuk digunakan ulang.