6.1 KiB
6.1 KiB
好的,下面是对修正后的 Prim 算法 实现代码的逐行解析:
代码
import heapq
- 引入
heapq
库来使用优先队列(最小堆),该库提供了堆的基本操作,如插入元素和取出最小值。
class Graph:
def __init__(self, vertices):
self.V = vertices # 顶点数
self.graph = [] # 存储边的列表
Graph
类:表示图的结构,构造函数__init__
初始化图,接收顶点数量vertices
作为参数。self.V
:存储图的顶点数。self.graph
:一个空列表,用来存储图的所有边。
def add_edge(self, u, v, weight):
self.graph.append([u, v, weight])
add_edge
方法:向图中添加一条边。每条边由起点u
、终点v
和边的权重weight
构成,并存储为一个三元组[u, v, weight]
。
def prim_mst(self):
mst_set = [False] * self.V # 记录是否已加入 MST
edge_heap = [(0, 0, -1)] # (权值, 顶点, 父节点) (-1 表示起始节点无父节点)
total_cost = 0
mst_edges = []
prim_mst
方法:实现 Prim 算法,计算图的最小生成树(MST)。mst_set
:一个布尔列表,初始化为False
,用于记录每个顶点是否已经加入到生成树中。edge_heap
:一个最小堆,用于存储候选的边,每个元素是一个三元组(权值, 顶点, 父节点)
。初始时,将起点0
的权值设为0
,父节点为-1
。total_cost
:记录生成树的总权重。mst_edges
:用于存储最小生成树的边。
while edge_heap and len(mst_edges) < self.V - 1:
weight, u, parent = heapq.heappop(edge_heap)
if mst_set[u]: # 忽略已在 MST 中的节点
continue
while edge_heap and len(mst_edges) < self.V - 1
:循环直到最小生成树包含V-1
条边,或者没有更多的边可供选择。heapq.heappop(edge_heap)
:从最小堆中取出权值最小的边(weight, u, parent
)。if mst_set[u]
: 如果当前顶点u
已经在最小生成树中,跳过这个顶点。
mst_set[u] = True
total_cost += weight
mst_set[u] = True
:将顶点u
标记为已加入最小生成树。total_cost += weight
:将当前边的权重weight
加到total_cost
上。
if parent != -1:
mst_edges.append((parent, u, weight))
if parent != -1
:只有在顶点u
的父节点不为-1
时,才将该边添加到mst_edges
中。起始节点0
的父节点为-1
,不会记录为边。
for v, w in self._adjacent_edges(u):
if not mst_set[v]:
heapq.heappush(edge_heap, (w, v, u))
_adjacent_edges
方法(稍后分析)返回与顶点u
相连的所有边。- 遍历与
u
相邻的所有顶点v
和边的权重w
,如果v
尚未加入最小生成树 (mst_set[v] == False
),则将边(w, v, u)
添加到优先队列中。
return total_cost, mst_edges
- 返回最小生成树的总权重
total_cost
和所有的生成树边mst_edges
。
def _adjacent_edges(self, u):
# 返回与顶点 u 相连的所有边
return [(v, weight) for (u_, v, weight) in self.graph if u_ == u] + \
[(u, weight) for (u, v_, weight) in self.graph if v_ == u]
_adjacent_edges
方法:返回所有与顶点u
相连的边。由于图是无向图,因此需要分别查找以u
为起点的边和以u
为终点的边。
def kruskal_mst(self):
self.graph.sort(key=lambda x: x[2]) # 按权值排序
parent = list(range(self.V))
rank = [0] * self.V
kruskal_mst
方法:实现 Kruskal 算法,计算图的最小生成树。self.graph.sort(key=lambda x: x[2])
:首先按边的权重对图中的所有边进行排序。parent
:初始化每个顶点的父节点为自身(使用并查集结构)。rank
:用于优化并查集的秩(深度)信息,避免树的高度过高。
def find(u):
if parent[u] != u:
parent[u] = find(parent[u])
return parent[u]
find
函数:实现并查集中的查找操作,查找顶点u
的根节点,并通过路径压缩优化查找过程。
def union(u, v):
root_u = find(u)
root_v = find(v)
if rank[root_u] > rank[root_v]:
parent[root_v] = root_u
elif rank[root_u] < rank[root_v]:
parent[root_u] = root_v
else:
parent[root_v] = root_u
rank[root_u] += 1
union
函数:实现并查集中的合并操作,将两个集合合并为一个集合。- 根据秩优化合并操作,将较小的树合并到较大的树下面,保持树的平衡。
mst_edges = []
total_cost = 0
- 初始化最小生成树的边列表
mst_edges
和总权重total_cost
。
for u, v, weight in self.graph:
if find(u) != find(v):
union(u, v)
mst_edges.append((u, v, weight))
total_cost += weight
- 遍历所有的边,对于每一条边,如果边的两个端点属于不同的集合(即不在同一连通分量中),就将这条边加入最小生成树。
- 使用
find(u) != find(v)
判断是否属于不同的集合,若是,则合并它们。 - 将边
(u, v, weight)
加入到mst_edges
中,并累加权重weight
到total_cost
。
- 使用
return total_cost, mst_edges
- 返回 Kruskal 算法的总权重
total_cost
和生成树的边mst_edges
。
总结
- Prim 算法:通过最小堆逐步选择连接最小生成树的边,最终得到最小生成树。
- Kruskal 算法:通过边的权值排序和并查集来逐步选择最小的边,最终得到最小生成树。
两种算法的核心思想不同,但最终目标相同,都是求出图的最小生成树。