Selama dekade 1970-an, Cook, Karp, dan Levin membangun kerangka teori yang mengubah cara kita memandang kesulitan masalah komputasi: klasifikasi P, NP, NP-hard, dan NP-complete. Inti teori ini menjawab pertanyaan “Apakah masalah ini bisa diselesaikan dalam waktu polinomial?” — dan untuk ribuan masalah penting, jawabannya “Kami tidak tahu, tetapi bila salah satu bisa, semua bisa”. Pada Chapter ini kita mendefinisikan empat kelas kompleksitas, mendemonstrasikan algoritma brute-force untuk SAT (NP-complete klasik), menjelaskan konsep reduksi polinomial, dan menutup dengan daftar masalah NP-complete yang sering muncul di interview dan kompetisi. Pemahaman teori ini krusial agar kita tidak menghabiskan waktu mencari algoritma polinomial untuk masalah yang (kemungkinan besar) tidak memilikinya.
Aturan: Empat Kelas Kompleksitas #
| Kelas | Definisi formal | Intuisi |
|---|---|---|
| P | L ∈ P iff ada algoritma polinomial yang memutus L | Mudah: solusi O(n^k) untuk k konstan |
| NP | L ∈ NP iff solusi dapat diverifikasi polinomial | Solusi mudah dicek, walau mungkin sulit dicari |
| NP-hard | L NP-hard iff ∀L’ ∈ NP: L' ≤p L (semua NP reducible ke L) | Setara sulit dengan semua masalah NP |
| NP-complete | L NP-complete iff L ∈ NP dan L NP-hard | Yang tersulit di kelas NP |
Definisi Formal: Reduksi Polinomial #
A ≤p B (A reducible ke B secara polinomial) berarti: ada fungsi f yang dapat dihitung polinomial sehingga untuk setiap input x:
x ∈ A ⟺ f(x) ∈ BInti: bila kita punya algoritma polinomial untuk B, kita dapat algoritma polinomial untuk A dengan transformasi input via f lalu jalankan solver B. Maka B paling tidak sama mudahnya dengan A.
Hierarki #
Bila P = NP (masalah terbuka), maka NPC = P — semua masalah NP-complete punya solusi polinomial. Konsensus ilmiah: P ≠ NP, sehingga NP-complete tidak punya solusi polinomial.
Mekanisme: Brute-Force SAT dan Eksponensialitas #
SAT (Boolean Satisfiability): diberikan formula boolean dalam CNF (Conjunctive Normal Form), cari assignment variabel yang membuat formula true.
Contoh formula 2 variabel:
(x1 ∨ x2) ∧ (¬x1 ∨ x2) ∧ (x1 ∨ ¬x2) ∧ (¬x1 ∨ ¬x2)
Cek semua 4 assignment:
| x1 | x2 | C1=x1∨x2 | C2=¬x1∨x2 | C3=x1∨¬x2 | C4=¬x1∨¬x2 | Formula |
|---|---|---|---|---|---|---|
| F | F | F | T | T | T | F |
| F | T | T | T | F | T | F |
| T | F | T | F | T | T | F |
| T | T | T | T | T | F | F |
Semua assignment gagal — UNSATISFIABLE. Brute-force selalu O(2^n · m) di mana n = jumlah variabel dan m = jumlah clause. Untuk n = 30, ini sudah ~10^9 iterasi; n = 50 mustahil dalam waktu manusia.
Kode: Brute-Force SAT Solver #
#include <iostream>
#include <vector>
#include <string>
#include <cstdio>
using namespace std;
// SAT instance: variabel x1..xn, clause adalah list literal (positif = xi, negatif = NOT xi)
struct SATInstance {
int n_vars;
vector<vector<int>> clauses;
};
// Brute-force SAT: coba semua 2^n assignment. Kompleksitas O(2^n * m) di mana m = jumlah clause.
// Mengembalikan jumlah assignment yang dicoba (untuk demo kompleksitas).
long long sat_solve(const SATInstance& inst, vector<bool>& assignment, bool& found) {
int n = inst.n_vars;
long long tried = 0;
for (int mask = 0; mask < (1 << n); ++mask) {
++tried;
bool all_sat = true;
for (const auto& clause : inst.clauses) {
bool clause_sat = false;
for (int lit : clause) {
int var = abs(lit) - 1;
bool val = (mask >> var) & 1;
if (lit > 0 && val) { clause_sat = true; break; }
if (lit < 0 && !val) { clause_sat = true; break; }
}
if (!clause_sat) { all_sat = false; break; }
}
if (all_sat) {
assignment.assign(n, false);
for (int i = 0; i < n; ++i) assignment[i] = (mask >> i) & 1;
found = true;
return tried;
}
}
found = false;
return tried;
}
string fmt_clause(const vector<int>& c) {
string s = "(";
for (size_t i = 0; i < c.size(); ++i) {
if (i) s += " OR ";
if (c[i] < 0) s += "NOT ";
s += "x" + to_string(abs(c[i]));
}
s += ")";
return s;
}
string fmt_instance(const SATInstance& inst) {
string s;
for (size_t i = 0; i < inst.clauses.size(); ++i) {
if (i) s += " AND ";
s += fmt_clause(inst.clauses[i]);
}
return s;
}
int main() {
// Instance 1: UNSATISFIABLE
// (x1 OR x2) AND (NOT x1 OR x2) AND (x1 OR NOT x2) AND (NOT x1 OR NOT x2)
SATInstance unsat;
unsat.n_vars = 2;
unsat.clauses = {{1, 2}, {-1, 2}, {1, -2}, {-1, -2}};
cout << "Instance 1: " << fmt_instance(unsat) << '\n';
vector<bool> a1;
bool r1;
long long t1 = sat_solve(unsat, a1, r1);
cout << "Satisfiable? " << (r1 ? "YA" : "TIDAK") << '\n';
cout << "Assignment yang dicoba: " << t1 << " (2^n = " << (1 << unsat.n_vars) << ")\n\n";
// Instance 2: SATISFIABLE
// (x1 OR x2) AND (NOT x1 OR x2) AND (x1 OR NOT x2)
SATInstance sat;
sat.n_vars = 2;
sat.clauses = {{1, 2}, {-1, 2}, {1, -2}};
cout << "Instance 2: " << fmt_instance(sat) << '\n';
vector<bool> a2;
bool r2;
long long t2 = sat_solve(sat, a2, r2);
cout << "Satisfiable? " << (r2 ? "YA" : "TIDAK") << '\n';
if (r2) {
cout << "Assignment: x1=" << a2[0] << ", x2=" << a2[1] << '\n';
}
cout << "Assignment yang dicoba: " << t2 << " (2^n = " << (1 << sat.n_vars) << ")\n";
// Demo kompleksitas eksponensial: jumlah iterasi untuk n berbeda (instance UNSAT penuh)
cout << "\nDemo kompleksitas (instance UNSAT, coba semua 2^n):\n";
cout << " n | 2^n | iterasi\n";
cout << " ----|------------|------------\n";
for (int n : {5, 10, 15, 20}) {
// Instance UNSAT sederhana: (x1) AND (NOT x1)
SATInstance unsat_n;
unsat_n.n_vars = n;
unsat_n.clauses.push_back({1}); // x1 harus true
unsat_n.clauses.push_back({-1}); // x1 harus false
for (int i = 2; i <= n; ++i) {
unsat_n.clauses.push_back({i, -i}); // tautology, tidak mengubah satisfiability
}
vector<bool> a;
bool r;
long long it = sat_solve(unsat_n, a, r);
printf(" %3d | %10d | %lld\n", n, (1 << n), it);
}
return 0;
}Output yang Dihasilkan:
Instance 1: (x1 OR x2) AND (NOT x1 OR x2) AND (x1 OR NOT x2) AND (NOT x1 OR NOT x2)
Satisfiable? TIDAK
Assignment yang dicoba: 4 (2^n = 4)
Instance 2: (x1 OR x2) AND (NOT x1 OR x2) AND (x1 OR NOT x2)
Satisfiable? YA
Assignment: x1=1, x2=1
Assignment yang dicoba: 4 (2^n = 4)
Demo kompleksitas (instance UNSAT, coba semua 2^n):
n | 2^n | iterasi
----|------------|------------
5 | 32 | 32
10 | 1024 | 1024
15 | 32768 | 32768
20 | 1048576 | 1048576Bedah Kode Baris-per-Baris #
struct SATInstance { int n_vars; vector<vector<int>> clauses; }: variabelx1..xn. Clause = list literal; literal positif+iberartixi, negatif-iberarti¬xi.for (int mask = 0; mask < (1 << n); ++mask): iterasi semua2^nassignment. Bit ke-idarimaskmenentukan nilaixi(1= true,0= false).for (int lit : clause): cek setiap literal.abs(lit) - 1= indeks variabel (0-based).val = (mask >> var) & 1ekstrak bit variabel tersebut.if (lit > 0 && val) { clause_sat = true; break; }: literal positif satisfied bila variabel true. Break clause loop — clause sudah pasti satisfied.if (lit < 0 && !val) { clause_sat = true; break; }: literal negatif satisfied bila variabel false.if (!clause_sat) { all_sat = false; break; }: clause tidak satisfied → instance tidak satisfied untukmaskini. Lanjut kemaskberikutnya.- Instance 1 (UNSAT): 4 assignment semua gagal. Cek tabel di atas — kolom kanan semua
F. Brute-force mencoba semua4 = 2^2assignment. - Instance 2 (SAT): assignment
x1=T, x2=T(mask = 3) men-satisfy semua clause. Brute-force mencobamask = 0, 1, 2(gagal) lalumask = 3(sukses). Total4iterasi (karenamask = 3adalah iterasi ke-4). - Demo kompleksitas: instance UNSAT dengan
nvariabel. Tabel menunjukkan iterasi =2^ntepat. Untukn = 30→~10^9iterasi;n = 40→~10^12;n = 50→~10^15— di luar jangkauan komputer modern (1 GHz × 1 tahun =~3 × 10^16siklus).
Mekanisme: Reduksi Polinomial #
Untuk membuktikan masalah B NP-hard, kita lakukan reduksi dari masalah A yang sudah diketahui NP-hard ke B:
- Asumsi:
ANP-hard (mis. 3-SAT). - Konstruksi
f: untuk setiap inputxmasalahA, bangun inputf(x)masalahBdalam waktu polinomial. - Bukti ekuivalensi:
x ∈ A ⟺ f(x) ∈ B. Yaitu,xadalah “yes” instance untukAifff(x)adalah “yes” instance untukB. - Konklusi: bila
Bpunya solver polinomial, makaAjuga (viaf). KarenaANP-hard,Bjuga NP-hard.
Contoh Klasik: 3-SAT ≤p Vertex Cover #
Diberikan instance 3-SAT dengan m clause dan n variabel, bangun graph:
- Untuk setiap variabel
xi: dua nodeT_i(true) danF_i(false) dihubungkan edge. - Untuk setiap clause
(l1 ∨ l2 ∨ l3): triangle (3 node, satu per literal), dengan label literal. - Hubungkan setiap node triangle dengan node variable yang sesuai (mis. node untuk literal
xidi triangle connect keT_i; node untuk¬xiconnect keF_i).
Claim: 3-SAT instance satisfiable iff graph punya vertex cover ukuran k = n + 2m.
Sketsa bukti:
- (⇒) Bila assignment memuaskan: pilih
T_iatauF_isesuai assignment (1 per variabel →nvertex), lalu untuk setiap clause pilih 2 dari 3 triangle node yang tidak match literal yang true (2 per clause →2mvertex). Totaln + 2m. - (⇐) Bila vertex cover ukuran
n + 2m: edge variabelT_i - F_imemaksa pilih salah satu — menentukan assignment. Edge triangle-clause-to-variable memaksa setiap clause punya ≥ 1 literal true (bila tidak, ketiga node triangle harus dipilih, tapi hanya 2 yang di-cover, kontradiksi). Maka assignment men-satisfy 3-SAT.
Reduksi ini berjalan dalam O(n + m) — polinomial. Maka Vertex Cover NP-hard. Karena Vertex Cover ∈ NP (verifikasi polinomial), Vertex Cover NP-complete.
Cook-Levin Theorem #
Theorem (Cook 1971, Levin 1973): SAT adalah NP-complete.
Sketsa bukti: Untuk setiap masalah L ∈ NP, ada mesin Turing non-deterministik M yang memutus L dalam waktu polinomial p(n). Konstruksi formula SAT yang mengkodekan:
- Konfigurasi awal tape
Mberisi inputx. - Setiap langkah komputasi
Mvalid sesuai transisi. - Konfigurasi akhir adalah “accept”.
Formula ini berukuran polinomial dalam |x| (karena M berjalan polinomial), dan satisfiable iff M menerima x (untuk beberapa guess non-deterministik). Maka L ≤p SAT untuk semua L ∈ NP — artinya SAT NP-hard. Karena SAT ∈ NP (verifikasi assignment polinomial), SAT NP-complete.
Konsekuensi: setiap masalah NP dapat direduksi ke SAT. Ini dasar dari SAT solver modern (DPLL, CDCL) yang dipakai di industri (verifikasi hardware, planning, scheduling).
Masalah NP-Complete Klasik #
| Masalah | Deskripsi | Aplikasi |
|---|---|---|
| SAT / 3-SAT | Boolean formula satisfiable? | Verifikasi formal, planning |
| Vertex Cover | Cover semua edge dengan ≤ k vertex | Network monitoring |
| Independent Set | Set node independen ukuran ≥ k | Scheduling, kode anti-collision |
| Clique | Clique ukuran ≥ k | Social network analysis |
| Hamilton Path / Cycle | Path/Cycle yang visit semua node tepat sekali | Routing TSP |
| TSP (decision) | Tur cost ≤ K? | Logistik, delivery |
| Subset Sum | Subset angka berjumlah K? | Kriptografi (knapsack) |
| Graph Coloring | Color graph dengan ≤ k warna? | Register allocation, scheduling |
| Longest Path | Path panjang ≥ k | Bioinformatika (sequence alignment) |
| Knapsack (decision) | Total value ≥ V dengan weight ≤ W? | Resource allocation |
Semua masalah di atas dapat direduksi satu sama lain dalam waktu polinomial (jaringan reduksi). Bila satu ditemukan punya algoritma polinomial, semua juga punya.
Kenapa Ini Penting bagi Praktisi DSA #
Mengenali masalah NP-complete menghindari kita dari mengejar solusi polinomial yang (kemungkinan besar) tidak ada. Alih-alih, arahkan energi ke approximation atau heuristik.
Reduksi sebagai tool memungkinkan kita menggunakan SAT solver atau ILP solver untuk masalah sulit: encode masalah sebagai SAT instance, lempar ke solver industri (Minisat, Z3), dapat solusi dalam milidetik untuk input sedang.
Kompleksitas praktis vs teoretis: algoritma
O(2^n)untukn ≤ 30masih feasible dengan optimasi (mis. branch-and-bound). AlgoritmaO(n^3)untukn = 1000pun juga feasible. Konstanta sering kali lebih penting daripada asimptotik untuk input praktis.Open problem
P = NP: bila satu masalah NP-complete punya algoritma polinomial, makaP = NP— implikasi revolusioner untuk kriptografi (RSA hancur), AI (banyak masalah optim jadi mudah), dan teori komputasi. Konsensus:P ≠ NP, tetapi belum ada bukti formal.
Ringkasan #
- P = masalah dengan solusi polinomial; NP = masalah dengan verifikasi polinomial.
- NP-hard = paling tidak lebih mudah dari semua NP; NP-complete = NP-hard ∩ NP.
- Reduksi polinomial
A ≤p BmembuktikanBpaling tidak sama sulitnya denganA. - Cook-Levin theorem: SAT adalah NP-complete — semua masalah NP dapat direduksi ke SAT.
- Brute-force SAT kompleksitas
O(2^n · m)— eksponensial, tidak feasible untukn ≥ 30(1 miliar+ iterasi). - Strategi praktis: approximation algorithm, heuristik, SAT/ILP solver, parameterized complexity.
- Jangan mencari algoritma polinomial untuk masalah NP-complete kecuali
P = NP.