Dijkstra dan Bellman-Ford hanya menjawab satu pertanyaan: jarak dari satu sumber ke semua node. Namun banyak persoalan nyata membutuhkan jarak antar semua pasangan node — analisis sentralitas graph, perhitungan diameter network, transitive closure, atau query jarak dinamis. Floyd-Warshall menyelesaikan all-pairs shortest path (APSP) dengan elegan: dynamic programming tiga dimensi yang di-optimasi menjadi matriks dua dimensi, dalam waktu O(V³). Pada Chapter ini kita akan membedah recurrencenya, mengimplementasikannya dalam C++ yang ramping (~10 baris inti), dan melihat bagaimana algoritma ini juga mendeteksi siklus berbobot negatif.
Aturan: All-Pairs Shortest Path #
Diberikan graph berarah berbobot (boleh negatif), cari jarak terpendek antara setiap pasangan (i, j). Output: matriks dist[i][j] berisi jarak minimum, atau INF bila tidak terjangkau. Algoritma juga harus mendeteksi bila terdapat siklus berbobot negatif.
Mekanisme: DP over Intermediate Vertex #
Ide brilian Floyd-Warshall: bertanya bertahap, “berapa jarak i → j jika node intermediate yang diperbolehkan hanya {0, 1, ..., k}?” Notasi d[k][i][j] = jarak minimum dari i ke j menggunakan intermediate dari himpunan {0, 1, ..., k-1}.
Recurrence:
d[k][i][j] = min(d[k-1][i][j], // tidak lewat k
d[k-1][i][k] + d[k-1][k][j]) // lewat kBasis d[0][i][j] = bobot edge langsung i → j, atau INF bila tidak ada, atau 0 bila i == j.
Optimasi ruang: karena d[k] hanya bergantung pada d[k-1], kita bisa pakai matriks 2D dan update in-place. Loop k di luar, i dan j di dalam — urutan ini krusial untuk kebenaran.
Implementasi Floyd-Warshall #
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
const long long INF = LLONG_MAX / 4;
// Floyd-Warshall: all-pairs shortest path
// Mengembalikan (sukses, dist). sukses=false bila ada siklus berbobot negatif.
std::pair<bool, std::vector<std::vector<long long>>>
floyd_warshall(int n, std::vector<std::vector<long long>> w) {
// w adalah matriks adjacency: w[u][v] = bobot edge u->v, INF bila tidak ada
// Diagonal = 0
std::vector<std::vector<long long>> dist = w;
for (int i = 0; i < n; ++i) dist[i][i] = 0;
for (int k = 0; k < n; ++k) {
for (int i = 0; i < n; ++i) {
if (dist[i][k] == INF) continue;
for (int j = 0; j < n; ++j) {
if (dist[k][j] == INF) continue;
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
// Cek siklus negatif: dist[i][i] < 0 berarti ada siklus berbobot negatif
for (int i = 0; i < n; ++i) {
if (dist[i][i] < 0) return {false, {}};
}
return {true, dist};
}
int main() {
// Graph 4 node (directed, weighted, ada 1 edge negatif):
// 0 -(5)-> 1
// 0 -(2)-> 3
// 1 -(3)-> 2
// 2 -(-1)-> 3
// 3 -(4)-> 1
int n = 4;
std::vector<std::vector<long long>> w(n, std::vector<long long>(n, INF));
auto add = [&](int u, int v, long long weight) { w[u][v] = weight; };
add(0, 1, 5); add(0, 3, 2);
add(1, 2, 3);
add(2, 3, -1);
add(3, 1, 4);
auto [ok, dist] = floyd_warshall(n, w);
if (!ok) {
std::cout << "Siklus berbobot negatif terdeteksi!\n";
return 0;
}
std::cout << "Matriks jarak semua pasangan (INF = tidak terjangkau):\n";
std::cout << " ";
for (int j = 0; j < n; ++j) std::cout << j << " ";
std::cout << '\n';
for (int i = 0; i < n; ++i) {
std::cout << " " << i << " : ";
for (int j = 0; j < n; ++j) {
if (dist[i][j] == INF) std::cout << "INF ";
else std::cout << dist[i][j] << " ";
}
std::cout << '\n';
}
std::cout << "\nTrace:\n";
std::cout << " dist[0][1] = 5 (edge langsung 0->1)\n";
std::cout << " dist[0][2] = 8 (0->1->2 = 5+3)\n";
std::cout << " dist[0][3] = 2 (edge langsung 0->3)\n";
std::cout << " dist[1][3] = 2 (1->2->3 = 3+(-1) = 2, lebih murah dari INF)\n";
std::cout << " dist[2][1] = 3 (2->3->1 = (-1)+4 = 3, lebih murah dari INF)\n";
std::cout << " dist[3][2] = 7 (3->1->2 = 4+3 = 7, lebih murah dari INF)\n";
return 0;
}Output yang Dihasilkan:
Matriks jarak semua pasangan (INF = tidak terjangkau):
0 1 2 3
0 : 0 5 8 2
1 : INF 0 3 2
2 : INF 3 0 -1
3 : INF 4 7 0
Trace:
dist[0][1] = 5 (edge langsung 0->1)
dist[0][2] = 8 (0->1->2 = 5+3)
dist[0][3] = 2 (edge langsung 0->3)
dist[1][3] = 2 (1->2->3 = 3+(-1) = 2, lebih murah dari INF)
dist[2][1] = 3 (2->3->1 = (-1)+4 = 3, lebih murah dari INF)
dist[3][2] = 7 (3->1->2 = 4+3 = 7, lebih murah dari INF)Bedah Kode Baris-per-Baris #
std::vector<std::vector<long long>> w(n, std::vector<long long>(n, INF)): matriks adjacency.w[u][v]= bobot edge, atauINFbila tidak ada edge. Bobot bisa negatif.dist[i][i] = 0: inisialisasi diagonal. Jarak dari node ke dirinya sendiri = 0. Tanpa baris ini,dist[i][i]bisa saja diupdate melalui siklus (yang mungkin saja negatif → deteksi nanti).for (int k = 0; k < n; ++k): loop intermediate nodekdi luar. Urutan ini wajib — bilakdi dalam, algoritma tidak akan menghitung jalur multi-hop dengan benar.if (dist[i][k] == INF) continue;: optimasi praktis. Bilai → ktidak terjangkau, tidak ada gunanya mencoba updatei → k → j.if (dist[k][j] == INF) continue;: optimasi serupa untukk → j.dist[i][j] = dist[i][k] + dist[k][j]: in-place update. Karenadist[i][k]dandist[k][j]sudah optimal untuk intermediate{0..k-1}, dan kita sedang mengupdatedist[i][j]untuk intermediate{0..k}, update in-place setara dengand[k][i][j]= min(d[k-1][i][j],d[k-1][i][k]+d[k-1][k][j]).if (dist[i][i] < 0) return {false, {}}: deteksi siklus berbobot negatif. Biladist[i][i]menjadi negatif, artinya ada siklus dariikeidengan total bobot negatif — jarak bisa di--∞.dist[0][3] = 2bukan7: path0 → 3langsung (bobot 2) lebih pendek dari0 → 1 → 2 → 3(5 + 3 + (-1) = 7). Floyd-Warshall menemukan keduanya dan memilih minimum.dist[1][3] = 2: jalur terpendek1 → 2 → 3(3 + (-1) = 2), bukan edge langsung (tidak ada). Floyd-Warshall menemukan ini saatk=2dank=3.dist[2][1] = 3: jalur2 → 3 → 1((-1) + 4 = 3). Floyd-Warshall menemukan ini saatk=3karena di iterasik=3barudist[2][3]dandist[3][1]sudah optimal.
Trace Iterasi (Sketsa) #
| Tahap | dist[1][3] | dist[2][1] | dist[3][2] | dist[0][2] |
|---|---|---|---|---|
| Init | INF | INF | INF | INF |
| k=0 | INF | INF | INF | INF |
| k=1 | INF | INF | INF | 8 (0→1→2) |
| k=2 | 2 (1→2→3) | INF | INF | 8 |
| k=3 | 2 | 3 (2→3→1) | 7 (3→1→2) | 8 |
Setelah k=n-1, semua pasangan sudah optimal (atau siklus negatif terdeteksi).
Kompleksitas #
| Aspek | Nilai | Catatan |
|---|---|---|
| Waktu | O(V³) | Tiga loop bersarang. Tidak ada optimasi asimptotik. |
| Ruang | O(V²) | Matriks dist. Bisa dilacak path dengan matriks next[][] tambahan. |
| Bobot negatif | Ya | Mendukung, dengan deteksi siklus negatif. |
| Cocok untuk | V ≤ 500 | V=1000 → 10⁹ operasi (batas waktu 1 detik). |
Komparasi APSP: Floyd-Warshall vs Dijkstra Beruntun #
| Aspek | Floyd-Warshall | Dijkstra × V |
|---|---|---|
| Kompleksitas | O(V³) | O(V·(V+E) log V) = O(V² log V + V·E log V) |
| Implementasi | ~10 baris | Lebih panjang |
| Bobot negatif | Ya | Tidak (perlu Johnson’s reweighting) |
| Sparse graph | Terlalu boros | Dijkstra lebih cepat |
| Dense graph | Kompetitif | Setara |
Aplikasi Nyata #
- Transitive closure graph:
dist[i][j] < INF→idapat mencapaij. Modifikasi: set boolean reachable. - Network diameter: maksimum dari
dist[i][j]untuk semua pasangan. - Centrality analysis: hitung betweenness centrality (node yang sering jadi intermediate).
- Routing table: pada network kecil, semua jarak precomputed sekali untuk query cepat.
- Regular expression matching: implementasi Kleene’s algorithm yang analog.
Rekonstruksi Path (Bonus) #
Untuk mendapatkan path aktual i → ... → j (bukan hanya jarak), simpan matriks next[i][j] yang menyimpan node intermediate pertama dari i ke j:
// Saat update:
if (dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
next[i][j] = next[i][k]; // atau k, tergantung konvensi
}
// Rekonstruksi:
std::vector<int> path;
for (int cur = i; cur != j; cur = next[cur][j]) path.push_back(cur);
path.push_back(j);Ringkasan #
- Floyd-Warshall menyelesaikan APSP dengan DP tiga dimensi yang dioptimasi menjadi matriks 2D.
- Recurrence:
d[k][i][j] = min(d[k-1][i][j], d[k-1][i][k] + d[k-1][k][j]). - Loop
kdi luar,idanjdi dalam — urutan ini wajib. - Kompleksitas
O(V³)waktu,O(V²)ruang. - Mendukung bobot negatif dan mendeteksi siklus berbobot negatif (
dist[i][i] < 0). - Cocok untuk
V ≤ 500atau graph dense.
Bab berikutnya beralih ke topik baru: Minimum Spanning Tree — definisi, properti, dan ikhtisar algoritma MST klasik.