Prim-and-Kruskal/gpt/explain.md
2024-12-16 16:42:11 +08:00

145 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

好的,下面是对修正后的 **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 算法**通过边的权值排序和并查集来逐步选择最小的边最终得到最小生成树
两种算法的核心思想不同但最终目标相同都是求出图的最小生成树