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

6.1 KiB
Raw Permalink Blame History

好的,下面是对修正后的 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 中,并累加权重 weighttotal_cost
        return total_cost, mst_edges
  • 返回 Kruskal 算法的总权重 total_cost 和生成树的边 mst_edges

总结

  • Prim 算法:通过最小堆逐步选择连接最小生成树的边,最终得到最小生成树。
  • Kruskal 算法:通过边的权值排序和并查集来逐步选择最小的边,最终得到最小生成树。

两种算法的核心思想不同,但最终目标相同,都是求出图的最小生成树。