最小生成树是指,在边有权重的连通无向图上,图的总权重最小的连通子集(所有的结点都被连通,选取的边具有最小的权重和)
本章的两种算法 Kruskal 算法和 Prim 算法都应用了贪心策略
最小生成树的形成
构建最小生成树的策略:
构建循环不变式,在每遍循环之前,边集合 A 都是某颗最小生成树的一个子集。每遍循环中做的事是,寻找安全边,并将其加入到集合 A 中。
安全边的定义就是不会破坏上述循环不变式的边。通俗的说,集合 A 是某颗最小生成树的子集,如果一条边被加入 A 中后,A 仍然是某颗最小生成树的子集,那么这条边就是安全边。
几个定义:切割、横跨切割的边、切割尊重集合 A、轻量级边
定理 23.1 给出了识别安全边的规则,通俗的描述就是,A 是一个包括在某颗最小生成树中的集合,S 为尊重 A 的一个切割,那么横跨 S 的轻量级边就是安全边。
推论 23.2 指出了寻找安全边的方法,这个描述令我很混乱,也没有看出来这个推论是如何指导下一节的两个算法的。仔细思考后理解了推论的意思,之前迷惑的地方在于推论中说 C 是森林 GA 的一个连通分量,而证明中说切割 VC 尊重集合 A,这里应该这样理解,连通分量 C 是森林中的一个孤岛,与外界相连的边都不在集合 A 中,切割 VC 把集合 A 的其它连通分量分割在了另一边,而这一边只有连通分量 C
Kruskal 算法和 Prim 算法
Kruskal 算法
Kruskal 算法的思路是,在所有连接森林中两颗不同树的边里面,找到权重最小的边(应用到了推论 23.2,树即连通分量,树对外连接的轻量级边都是安全边)
算法的实现用到了“不相交集合数据结构(第 21 章)”,步骤是:
- 先将每个结点生成一个集合
- 按权重升序的方式将边排序
- 按上面排序结果遍历所有边,如果一条边的两个结点不属于同一个集合(边连接两个不同的连通分量),则将该边加入集合 A,并将两个结点所属的集合合并
Kruskal 算法的性能依赖于选用的“不相交集合数据结构”的性能,书中按“增加按秩合并和路径压缩的不想交集合森林”讨论,经过一系列酷炫的推导,得出结论:Kruskal 算法的运行时间为 O(ElgV)
Prim 算法
Prim 算法的思路是,每一步在连接集合 A 和 A 之外的结点的所有边中,选择一条轻量级边加入到 A 中
集合 A 中的边总是构成一棵树(集合 A 是由一个结点不停的扩展,直到成为一颗包含所有结点的树,这个过程中集合 A 一直是一棵树,区别于 Kruskal 算法中集合 A 是一个森林)
算法的实现用到了“最小优先队列”,步骤是:
- 为所有结点都初始化两个额外的属性 key 和 π,其中 key 是该结点和 A 中结点连接的所有边中最小的权重值,初始化值为 ∞,π 是该结点在树中的父节点,初始化值为 nil
- 将所有结点加入最小优先队列 Q(根据结点的 key 构建)
- 对 Q 取最小结点进行循环,直至 Q 为空。每次循环对当前最小结点的所有连接结点进行遍历,比较更新连接结点的 key 值和 π 值
Prim 算法的运行时间取决于 Q 的实现方式。如果使用二叉最小优先队列,运行时间为 O(ElgV)。如果使用斐波那契堆,运行时间为 O(E+VlgV)
网友评论