Pada Chapter MST kita melihat bahwa cycle property menjadi dasar sebuah algoritma elegan: urutkan semua edge dari bobot terkecil ke terbesar, lalu tambahkan edge ke MST satu per satu — selama tidak membentuk siklus. Inilah algoritma Kruskal, diterbitkan Joseph Kruskal pada 1956. Strategi sederhana ini membutuhkan satu struktur pendukung penting: Disjoint Set Union (DSU) yang mengecek apakah dua endpoint edge sudah berada di komponen yang sama (yang berarti menambahkan edge akan membentuk siklus). Pada Chapter ini kita akan membedah mekanisme Kruskal, mengimplementasikannya dengan DSU ber-path compression + union by rank, dan melihat kenapa kompleksitasnya O(E log E).
Aturan: Tambah Edge Bila Tidak Siklus #
Diberikan graph tak berarah terhubung berbobot G = (V, E, w), algoritma Kruskal:
- Urutkan semua edge ascending by bobot.
- Inisialisasi DSU dengan
Vset singleton (satu set per node). - Iterasi edge yang sudah di-sort: untuk setiap edge
(u, v, w), bilafind(u) != find(v)(u dan v di komponen berbeda), gabungkanudanvserta tambahkan edge ke MST. Bila sama, skip (akan membentuk siklus). - Berhenti saat MST berisi
V - 1edge.
Mekanisme: DSU dengan Path Compression + Union by Rank #
DSU menyimpan setiap node dalam sebuah set (komponen). Operasi utama:
find(x): cari representatif (root) dari set yang berisix. Path compression: setiap node di sepanjang path darixke root langsung di-point ke root — mempercepat query berikutnya.unite(a, b): gabungkan dua set. Union by rank: jadikan root dengan rank lebih kecil menjadi anak dari root dengan rank lebih besar — menjamin pohon tetap pendek.
Kedua optimasi bersama memberi kompleksitas amortized O(α(n)) per operasi, di mana α adalah inverse Ackermann — praktis konstan untuk n realistis.
Implementasi Kruskal dengan DSU #
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <tuple>
struct DSU {
std::vector<int> parent, rank_;
explicit DSU(int n) : parent(n), rank_(n, 0) {
std::iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool unite(int a, int b) {
a = find(a); b = find(b);
if (a == b) return false;
if (rank_[a] < rank_[b]) std::swap(a, b);
parent[b] = a;
if (rank_[a] == rank_[b]) rank_[a]++;
return true;
}
};
// Kruskal: return (total_bobot, list_edge_MST)
std::pair<long long, std::vector<std::pair<int,int>>>
kruskal(int n, std::vector<std::tuple<int,int,int>> edges) {
std::sort(edges.begin(), edges.end(),
[](const auto& a, const auto& b) { return std::get<2>(a) < std::get<2>(b); });
DSU dsu(n);
long long total = 0;
std::vector<std::pair<int,int>> mst;
mst.reserve(n - 1);
for (auto [u, v, w] : edges) {
if ((int)mst.size() == n - 1) break; // optimasi: MST lengkap
if (dsu.unite(u, v)) {
mst.push_back({u, v});
total += w;
}
}
if ((int)mst.size() != n - 1) {
// Graph tidak terhubung: tidak ada MST
return {-1, {}};
}
return {total, mst};
}
int main() {
// Graph 6 node, edge berbobot:
// 0 -(7)- 1 -(8)- 2
// | | |
// (5) (9) (5)
// v v v
// 3 -(15)-4 -(6)- 5
// | |
// (6) (4) -- edge 3-5
int n = 6;
std::vector<std::tuple<int,int,int>> edges = {
{0, 1, 7}, {1, 2, 8}, {0, 3, 5}, {1, 4, 9}, {2, 5, 5},
{3, 4, 15}, {4, 5, 6}, {3, 5, 6}
};
auto [total, mst] = kruskal(n, edges);
if (total == -1) {
std::cout << "Graph tidak terhubung - tidak ada MST!\n";
return 0;
}
std::cout << "Kruskal MST:\n";
std::cout << " Total bobot: " << total << "\n";
std::cout << " Edge MST (urutan penambahan):\n";
for (auto [u, v] : mst) {
std::cout << " " << u << " - " << v << "\n";
}
std::cout << "\nEdge di-sort ascending, lalu ditambahkan bila tidak siklus.\n";
return 0;
}Output yang Dihasilkan:
Kruskal MST:
Total bobot: 29
Edge MST (urutan penambahan):
0 - 3
2 - 5
4 - 5
3 - 5
0 - 1
Edge di-sort ascending, lalu ditambahkan bila tidak siklus.Bedah Kode Baris-per-Baris #
std::vector<int> parent, rank_;: dua array DSU.parent[x]= parent nodex(atauxsendiri bila root).rank_[x]= perkiraan tinggi subtree.std::iota(parent.begin(), parent.end(), 0): inisialisasiparent[i] = i— setiap node adalah root-nya sendiri (singleton set).int find(int x): iteratif dengan path compression. Loopwhile (parent[x] != x)naik sampai root. Barisparent[x] = parent[parent[x]]adalah path halving — varian path compression yang lebih cache-friendly daripada full compression rekursif.bool unite(int a, int b): gabungkan dua set. Bilafind(a) == find(b)(sudah satu set), return false — edge akan membentuk siklus.if (rank_[a] < rank_[b]) std::swap(a, b);: union by rank. Root dengan rank lebih besar menjadi parent. Ini menjaga pohon tetap pendek.if (rank_[a] == rank_[b]) rank_[a]++: bila kedua root punya rank sama, rank hasil gabungan naik 1.std::sort(edges.begin(), edges.end(), ...): urutkan edge ascending by weight. KompleksitasO(E log E)— inilah biaya dominan Kruskal.if ((int)mst.size() == n - 1) break;: optimasi early stop. Setelah MST berisiV - 1edge, edge sisanya pasti membentuk siklus — tidak perlu diproses.if (dsu.unite(u, v)): cek dan gabungkan sekaligus. Bila sukses (komponen berbeda), tambahkan edge ke MST. Bila gagal, skip otomatis.return {-1, {}}: graph tidak terhubung. Hasil: spanning forest, bukan MST tunggal.- Hasil
total = 29: edge MST adalah{0-3 (5), 2-5 (5), 4-5 (6), 3-5 (6), 0-1 (7)}. Total =5+5+6+6+7 = 29.
Trace Step-by-Step #
Edge setelah sort ascending: 0-3(5), 2-5(5), 4-5(6), 3-5(6), 0-1(7), 1-2(8), 1-4(9), 3-4(15).
| Step | Edge | Bobot | find(u), find(v) | Aksi | MST Size | Total |
|---|---|---|---|---|---|---|
| 1 | 0-3 | 5 | 0, 3 (beda) | unite, add | 1 | 5 |
| 2 | 2-5 | 5 | 2, 5 (beda) | unite, add | 2 | 10 |
| 3 | 4-5 | 6 | 4, 5 (beda) | unite, add | 3 | 16 |
| 4 | 3-5 | 6 | 3, 5 (beda; 5 root adalah 2 via 5-2 union tadi; 3 root adalah 0) | unite, add | 4 | 22 |
| 5 | 0-1 | 7 | 0, 1 (beda) | unite, add | 5 | 29 |
| Stop | (MST = V-1 = 5) | — | — | break | — | — |
Edge 1-2 (8), 1-4 (9), 3-4 (15) tidak diproses karena MST sudah lengkap. Tanpa optimasi early stop, ketiga edge akan di-cek dan di-skip (membentuk siklus).
Kompleksitas #
| Aspek | Nilai | Catatan |
|---|---|---|
| Waktu | O(E log E) | Sort + E operasi DSU O(α(V)). |
| Ruang | O(V + E) | List edge + DSU. |
Sparse graph (E << V²) | Baik | Sort cepat, DSU ringan. |
Dense graph (E ≈ V²) | Setara Prim | Sort tetap O(E log E) = O(V² log V). |
| Graph tidak terhubung | Menghasilkan forest | Perlu cek size MST. |
Komparasi Kruskal vs Prim #
| Aspek | Kruskal | Prim |
|---|---|---|
| Strategi | Sort edge + DSU | Priority queue greedy |
| Kompleksitas | O(E log E) | O((V+E) log V) |
| Input ideal | List edge | Adjacency list/matrix |
| Membutuhkan | DSU | Priority queue |
| Sparse graph | Lebih cepat | Setara |
| Dense graph | Setara | Lebih cepat (versi matrix O(V²)) |
| Multi-MST | Otomatis (per komponen) | Perlu inisialisasi per komponen |
Variasi dan Penerapan #
- Second-best MST: setelah MST ditemukan, coba hapus satu edge MST, tambah satu edge non-MST terdekat — cari yang menghasilkan total minimum.
- Minimum spanning forest: bila graph tidak terhubung, jalankan Kruskal penuh tanpa early stop → spanning forest dengan satu MST per komponen.
- Dynamic MST: update MST bila ada edge yang berubah bobot. Lebih kompleks; algoritma Frederickson-JáJá.
- Euclidean MST: titik pada bidang, bobot = jarak Euclidean. Bisa dipecahkan dengan Kruskal setelah Delaunay triangulation.
Aplikasi Nyata #
- Network design: jaringan telepon/listrik/air minimum.
- Clustering (single-linkage): bangun MST dari distance matrix, potong edge terbesar
k-1untukkcluster. - Image segmentation: graph pixel → MST → potong edge dengan bobot tinggi → segment.
- Approximation TSP: dua kali MST traversal = tur TSP dengan biaya ≤ 2× optimal (metric TSP).
- Phylogenetic tree: MST dari distance matrix antar spesies (pendekatan sederhana).
Ringkasan #
- Kruskal membangun MST dengan strategi sort ascending + DSU cek siklus.
- DSU dengan path compression + union by rank memberi
O(α(n))per operasi. - Kompleksitas total
O(E log E)— didominasi sort. - Early stop setelah
V - 1edge masuk MST. - Cocok untuk graph sparse dan input berupa list edge.
- Aplikasi: network design, clustering, image segmentation, TSP approximation.
Bab berikutnya akan membahas Prim — algoritma MST yang menggunakan strategi berbeda: greedy dari satu node dengan priority queue.