目標
在連通網的所有生成樹中,找到所有邊的代價和最小的生成樹,簡稱最小生成樹問題.
(簡要的來說,就是在AOV網中找出串聯n個頂點代價總和最小的邊集)
下面記錄最小生成樹的兩種算法,Prim和Kruskal
Prim算法思路
- 從任意一個頂點開始,每次選擇與當前頂點最近的一個頂點,並將兩點之間的邊加入到樹中
- 被選中的點構成一個集合,剩下的點是候選集
- 每次從已選擇的點的集合中,查找花費最小的點,加入進來
- 同時在候選集中刪去,
- 重復3和4,知道候選集中沒有元素。
Prim算法代碼
def cmp(key1, key2):
return (key1, key2) if key1 < key2 else (key2, key1)
def prim(graph, init_node):
visited = {init_node}
candidate = set(graph.keys())
candidate.remove(init_node) # add all nodes into candidate set, except the start node
tree = []
while len(candidate) > 0:
edge_dict = dict()
for node in visited: # find all visited nodes
for connected_node, weight in graph[node].items(): # find those were connected
if connected_node in candidate:
edge_dict[cmp(connected_node, node)] = weight
edge, cost = sorted(edge_dict.items(), key=lambda kv: kv[1])[0] # find the minimum cost edge
tree.append(edge)
visited.add(edge[0]) # cause you dont know which node will be put in the first place
visited.add(edge[1])
candidate.discard(edge[0]) # same reason. discard wont raise an exception.
candidate.discard(edge[1])
return tree
if __name__ == '__main__':
graph_dict = {
"A": {"B": 7, "D": 5},
"B": {"A": 7, "C": 8, "D": 9, "E": 5},
"C": {"B": 8, "E": 5},
"D": {"A": 5, "B": 9, "E": 15, "F": 6},
"E": {"B": 7, "C": 5, "D": 15, "F": 8, "G": 9},
"F": {"D": 6, "E": 8, "G": 11},
"G": {"E": 9, "F": 11}
}
path = prim(graph_dict, "D")
print(path) # [('A', 'D'), ('D', 'F'), ('A', 'B'), ('B', 'E'), ('C', 'E'), ('E', 'G')]
與Prim算法關注圖的點不同,Kruskal算法更關注圖中的邊。
Kruskal算法思路
- 首先對圖中所有的邊進行遞增排序,排序標准是每條邊的權值
- 依次遍歷每條邊,如果這條邊加進去之后,不會使圖形成環,那就加進去,否則放棄
Kruskal算法雖然看起來思路清晰,但是如何判斷圖中是否成環,比較難理解。
Kruskal算法代碼
def cmp(key1, key2):
return (key1, key2) if key1 < key2 else (key2, key1)
def find_parent(record, node):
if record[node] != node:
record[node] = find_parent(record, record[node])
return record[node]
def naive_union(record, edge):
u, v = find_parent(record, edge[0]), find_parent(record, edge[1])
record[u] = v
def kruskal(graph, init_node):
edge_dict = {}
for node in graph.keys():
edge_dict.update({cmp(node, k): v for k, v in graph[node].items()})
sorted_edge = list(sorted(edge_dict.items(), key=lambda kv: kv[1]))
tree = []
connected_records = {key: key for key in graph.keys()}
for edge_pair, _ in sorted_edge:
if find_parent(connected_records, edge_pair[0]) != \
find_parent(connected_records, edge_pair[1]):
tree.append(edge_pair)
naive_union(connected_records, edge_pair)
return tree
if __name__ == '__main__':
graph_dict = {
"A": {"B": 7, "D": 5},
"B": {"A": 7, "C": 8, "D": 9, "E": 5},
"C": {"B": 8, "E": 5},
"D": {"A": 5, "B": 9, "E": 15, "F": 6},
"E": {"B": 7, "C": 5, "D": 15, "F": 8, "G": 9},
"F": {"D": 6, "E": 8, "G": 11},
"G": {"E": 9, "F": 11}
}
path = kruskal(graph_dict, "D")
print(path) # [('A', 'D'), ('D', 'F'), ('A', 'B'), ('B', 'E'), ('C', 'E'), ('E', 'G')]