已發布·持續改進
Algorithm
併查集(不相交集合)把元素劃分為互不重疊的群組,能以近乎常數的時間合併兩個群組或判斷兩個元素是否屬於同一群組。
併查集(Union-Find)又稱不相交集合資料結構,用來維護一組被劃分為若干互不重疊集合的元素。它只提供兩種操作:find 回傳元素所在集合的代表元,union 把兩個元素所在的集合合併為一個。每個集合以父指標構成的樹來儲存,樹根就是該集合的代表元。
當連結只增不減、又需要反覆詢問「這兩個元素是否連通」時,併查集比每次走訪圖快得多。同時使用路徑壓縮和依秩或依大小合併後,每次操作的攤銷成本降到反阿克曼函數 α(n),對任何現實規模的輸入它都不超過 4。它是 Kruskal 最小生成樹、環偵測、連通分量計算以及帳號合併等分組問題的標準工具,也是演算法面試和程式解題的常見考點。
建議先用一個 parent 陣列親手實作 find 和 union,並手動追蹤在沒有最佳化時樹如何退化成一條鏈。接著逐一加入依大小合併和路徑壓縮,觀察樹的深度如何變化,原理就會變得清楚。最後練習連通分量、環偵測和 Kruskal 等題目,養成善用 union 回傳值的習慣。
find 沿父指標往上找到根(代表元),union 把一個根接到另一個根之下,藉此合併兩個集合。
find 把經過的節點直接連到根上,讓樹變得扁平,之後的呼叫更快。
把較小的樹接到較大的樹下,使每棵樹的高度不超過 log n。
兩種最佳化同時使用時,m 次操作只需 O(m α(n)),而反阿克曼函數 α(n) 在實務上不超過 4。
DSU 類別用 parent 和 size 兩個陣列儲存集合。find 沿父指標往上,並透過路徑減半(把每個節點連到其祖父節點)讓樹保持扁平;union 把較小的集合接到較大的集合下,若兩個元素已在同一集合則回傳 False。範例把 6 個元素合併成三組,然後輸出是否屬於同一集合以及集合的數量。
union_find.py
class DSU:
def __init__(self, n):
self.parent = list(range(n))
self.size = [1] * n
def find(self, x):
while self.parent[x] != x:
self.parent[x] = self.parent[self.parent[x]] # path halving
x = self.parent[x]
return x
def union(self, a, b):
ra, rb = self.find(a), self.find(b)
if ra == rb:
return False
if self.size[ra] < self.size[rb]:
ra, rb = rb, ra
self.parent[rb] = ra
self.size[ra] += self.size[rb]
return True
dsu = DSU(6)
for a, b in [(0, 1), (1, 2), (3, 4)]:
dsu.union(a, b)
print(dsu.find(2) == dsu.find(0)) # True
print(dsu.find(4) == dsu.find(5)) # False
print(len({dsu.find(x) for x in range(6)})) # 3
python union_find.py共六章,帶你從安裝一步步認識 併查集 的核心概念。
在這裡提問、分享經驗,交流關於 併查集 的看法。
還沒有討論。來發起第一則吧。
0 則留言
登入 · 登入後即可留言。
來留下第一則留言吧。