145 lines
6.1 KiB
Markdown
145 lines
6.1 KiB
Markdown
好的,下面是对修正后的 **Prim 算法** 实现代码的逐行解析:
|
||
|
||
### 代码
|
||
|
||
```python
|
||
import heapq
|
||
```
|
||
- 引入 `heapq` 库来使用优先队列(最小堆),该库提供了堆的基本操作,如插入元素和取出最小值。
|
||
|
||
```python
|
||
class Graph:
|
||
def __init__(self, vertices):
|
||
self.V = vertices # 顶点数
|
||
self.graph = [] # 存储边的列表
|
||
```
|
||
- **`Graph` 类**:表示图的结构,构造函数 `__init__` 初始化图,接收顶点数量 `vertices` 作为参数。
|
||
- `self.V`:存储图的顶点数。
|
||
- `self.graph`:一个空列表,用来存储图的所有边。
|
||
|
||
```python
|
||
def add_edge(self, u, v, weight):
|
||
self.graph.append([u, v, weight])
|
||
```
|
||
- **`add_edge` 方法**:向图中添加一条边。每条边由起点 `u`、终点 `v` 和边的权重 `weight` 构成,并存储为一个三元组 `[u, v, weight]`。
|
||
|
||
```python
|
||
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`:用于存储最小生成树的边。
|
||
|
||
```python
|
||
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` 已经在最小生成树中,跳过这个顶点。
|
||
|
||
```python
|
||
mst_set[u] = True
|
||
total_cost += weight
|
||
```
|
||
- `mst_set[u] = True`:将顶点 `u` 标记为已加入最小生成树。
|
||
- `total_cost += weight`:将当前边的权重 `weight` 加到 `total_cost` 上。
|
||
|
||
```python
|
||
if parent != -1:
|
||
mst_edges.append((parent, u, weight))
|
||
```
|
||
- `if parent != -1`:只有在顶点 `u` 的父节点不为 `-1` 时,才将该边添加到 `mst_edges` 中。起始节点 `0` 的父节点为 `-1`,不会记录为边。
|
||
|
||
```python
|
||
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)` 添加到优先队列中。
|
||
|
||
```python
|
||
return total_cost, mst_edges
|
||
```
|
||
- 返回最小生成树的总权重 `total_cost` 和所有的生成树边 `mst_edges`。
|
||
|
||
```python
|
||
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` 为终点的边。
|
||
|
||
```python
|
||
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`:用于优化并查集的秩(深度)信息,避免树的高度过高。
|
||
|
||
```python
|
||
def find(u):
|
||
if parent[u] != u:
|
||
parent[u] = find(parent[u])
|
||
return parent[u]
|
||
```
|
||
- **`find` 函数**:实现并查集中的查找操作,查找顶点 `u` 的根节点,并通过路径压缩优化查找过程。
|
||
|
||
```python
|
||
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` 函数**:实现并查集中的合并操作,将两个集合合并为一个集合。
|
||
- 根据秩优化合并操作,将较小的树合并到较大的树下面,保持树的平衡。
|
||
|
||
```python
|
||
mst_edges = []
|
||
total_cost = 0
|
||
```
|
||
- 初始化最小生成树的边列表 `mst_edges` 和总权重 `total_cost`。
|
||
|
||
```python
|
||
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`。
|
||
|
||
```python
|
||
return total_cost, mst_edges
|
||
```
|
||
- 返回 Kruskal 算法的总权重 `total_cost` 和生成树的边 `mst_edges`。
|
||
|
||
---
|
||
|
||
### **总结**
|
||
- **Prim 算法**:通过最小堆逐步选择连接最小生成树的边,最终得到最小生成树。
|
||
- **Kruskal 算法**:通过边的权值排序和并查集来逐步选择最小的边,最终得到最小生成树。
|
||
|
||
两种算法的核心思想不同,但最终目标相同,都是求出图的最小生成树。 |