Union-Find memungkinkan merge dan query set secara hampir O(1) amortized. Di episode ini kalian memahami find dengan path compression, union by rank/size, serta mempraktikkan implementasi dari nol dan resolusi "friend circle" problem.

Setelah di episode 19 kita mempelajari Kruskal's algorithm yang menggunakan Union-Find, pada episode ini kita mempelajari Union-Find (Disjoint Set Union atau DSU) secara mendalam. Union-Find adalah struktur data sederhana namun powerful yang mendukung dua operasi utama: find (menentukan set mana sebuah elemen berada) dan union (menggabungkan dua set).
Dengan optimasi path compression dan union by rank/size, Union-Find mencapai waktu hampir O(1) amortized untuk setiap operasi — salah satu contoh terbaik dari "amortized analysis" dalam ilmu komputer.
Union-Find memanage disjoint sets — kumpulan set yang tidak memiliki elemen bersamaan. Dua operasi utama:
Tanpa optimasi, find dan union bisa O(n) dalam kondisi terburuk — tree menjadi sangat tinggi dan miring.
Saat find(x), ubah parent dari setiap node di jalur menjadi root secara langsung. Ini "memperpendek" tree untuk find berikutnya.
def find(x):
if parent[x] != x:
parent[x] = find(parent[x])
return parent[x]Saat union, gantung tree yang lebih pendek di bawah tree yang lebih tinggi. Ini menjaga tree tetap seimbang.
def union(x, y):
rx, ry = find(x), find(y)
if rx == ry:
return False
if rank[rx] < rank[ry]:
rx, ry = ry, rx
parent[ry] = rx
if rank[rx] == rank[ry]:
rank[rx] += 1
return TrueAlternatif: gantung tree yang lebih kecil (jumlah node lebih sedikit) di bawah tree yang lebih besar.
def union_by_size(x, y):
rx, ry = find(x), find(y)
if rx == ry:
return False
if size[rx] < size[ry]:
rx, ry = ry, rx
parent[ry] = rx
size[rx] += size[ry]
return Trueclass UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
self.size = [1] * n
self.components = n
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x])
return self.parent[x]
def union(self, x, y):
rx, ry = self.find(x), self.find(y)
if rx == ry:
return False
if self.rank[rx] < self.rank[ry]:
rx, ry = ry, rx
self.parent[ry] = rx
if self.rank[rx] == self.rank[ry]:
self.rank[rx] += 1
self.size[rx] += self.size[ry]
self.components -= 1
return True
def connected(self, x, y):
return self.find(x) == self.find(y)
def get_size(self, x):
return self.size[self.find(x)]def find_circle_num(is_connected):
n = len(is_connected)
uf = UnionFind(n)
for i in range(n):
for j in range(i + 1, n):
if is_connected[i][j]:
uf.union(i, j)
return uf.components
matrix = [
[1, 1, 0],
[1, 1, 0],
[0, 0, 1]
]
print("Friend circles:", find_circle_num(matrix))Tip
Union-Find dengan path compression dan union by rank mencapai O(α(n)) amortized — di mana α adalah inverse Ackermann function. Untuk semua input praktis, α(n) ≤ 4, sehingga operasi Union-Find dianggap hampir O(1).
Inti yang harus dibawa pulang:
Di episode 21 selanjutnya kita akan membahas bloom filter dan count-min sketch — struktur data probabilistik yang mengorbankan akurasi sempurna untuk efisiensi memori yang luar biasa. Bloom filter memberikan O(1) insert/lookup dengan false positive tapi tidak false negative — sangat berguna untuk cache, anti-spam, dan deduplication!