在前幾天,我們學習了最短路徑問題 (Shortest Path),例如 Dijkstra、Bellman-Ford、Floyd-Warshall、A*,今天要介紹的則是圖論中的另一個核心問題 —— 最小生成樹 (Minimum Spanning Tree, MST)
對於一個 連通、加權、無向圖:
應用場景:
兩個經典演算法:
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * 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):
root_x, root_y = self.find(x), self.find(y)
if root_x == root_y:
return False
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1
return True
def kruskal(V, edges):
uf = UnionFind(V)
mst = []
edges.sort(key=lambda x: x[2]) # 按照權重排序
for u, v, w in edges:
if uf.union(u, v):
mst.append((u, v, w))
return mst
# 測試
V = 4
edges = [
(0, 1, 10),
(0, 2, 6),
(0, 3, 5),
(1, 3, 15),
(2, 3, 4)
]
mst = kruskal(V, edges)
print("Kruskal MST:", mst)
# 輸出: [(2, 3, 4), (0, 3, 5), (0, 1, 10)]
import heapq
def prim(graph, start=0):
V = len(graph)
visited = [False] * V
mst = []
pq = [(0, start, -1)] # (權重, 當前節點, 來源節點)
while pq:
w, u, parent = heapq.heappop(pq)
if visited[u]:
continue
visited[u] = True
if parent != -1:
mst.append((parent, u, w))
for v, weight in graph[u]:
if not visited[v]:
heapq.heappush(pq, (weight, v, u))
return mst
# 測試
graph = {
0: [(1, 10), (2, 6), (3, 5)],
1: [(0, 10), (3, 15)],
2: [(0, 6), (3, 4)],
3: [(0, 5), (1, 15), (2, 4)]
}
mst = prim(graph, 0)
print("Prim MST:", mst)
# 輸出: [(0, 3, 5), (2, 3, 4), (0, 1, 10)]
特性 | Kruskal | Prim |
---|---|---|
思路 | 從邊出發,選最小邊,避免成環 | 從點出發,每次擴展最小邊 |
工具 | 並查集 (Union-Find) | 優先佇列 (Heap) |
適用情境 | 稀疏圖 (Sparse Graph) | 稠密圖 (Dense Graph) |
複雜度 | $O(E \log E)$ | $O(E \log V)$ |
結果 | MST | MST |
最小生成樹 (MST) 問題與最短路徑不同,它關心的是 整體的最小成本連接,而不是單點之間的最短距離。Kruskal 與 Prim 是兩種經典解法,分別適用於稀疏圖與稠密圖
在 Day 27 與 Day 28,我們會分別深入 Kruskal 與 Prim,帶大家更細緻地掌握這兩個演算法的細節與應用