定义:
一个连通无向图G=(V,E),对于属于E的每条边(u,v)有权重w(u,v),若能找到一个E的无环子集T,使得T能连接V中所有节点,(由此可知T必然是一棵树)且有最小的权重和,那么T就称为(图G的)最小生成树。而求解该树的问题便成为最小生成树问题。
下面主要讨论了解决该问题的两种算法:Kruskal算法和Prim算法。
使用不同的数据结构,将得到不同的时间复杂度。
Kruskal算法 | Prim算法 | |
二叉堆 | O(ElgV) | O(ElgV) |
斐波那契堆 | O(ElgV) | O(E + VlgV) |
这两种最小生成树算法都是贪心算法。在算法导论的十六章中指出,贪心算法推荐选择在当前看来最好的选择,但是这种策略一般不能保证找到一个全局最优的解决方案。
在介绍这两种最小生成树算法之前,先明确一些概念。
最小生成树的贪心策略可以由下面的通用方法来表述。
在每个时刻成长最小生成树的一条边,并在整个策略的实施过程中,管理一个遵守下述循环不变式的边集合A:
在每遍循环之前,A是某棵最小生成树的一个子集。
安全边:
在每一步中,选择一条边(u,v),将其加入到集合A中,使得A不违反循环不变式,即AU{(u,v)}也是某棵最小生成树的子集。由于我们可以安全地将这种边加入到集合A而不会破坏A的循环不变式,因此称这样的边为集合A的安全边。
无向图G=(V,E)的一个切割(S,V-S)是结合V的一个划分。如果一条属于E的边(u,v)一端位于集合S,另一端位于V-S,则称该边横跨切割(S,V-S)。如果集合A中不存在横跨该切割的边,则称该切割尊重集合A。在横跨一个切割的所有边中,权值最小的边称为轻量级边。
定理23.1
设G=(V,E)是一个在边E上定义了实数值权重函数w的连通无向图。集合A为E的一个子集,且A包括在图G的某棵最小生成树中,设(S,V-S)是图G中尊重集合A的任意一个切割,又设(u,v)是横跨切割的一条轻量级边。那么(u,v)对于集合A是安全的。
大致思路是:设T是包含子集A的一个最小生成树。
若集合AU{(u,v)}属于T,结论显然正确。
若集合AU{(u,v)}不属于T,我们可以找到另一个最小生成树T',使得AU{(u,v)}属于T',因此可知结论同样正确。pg364
由此,我们不难发现对于集合A安全的边,连接的都是GA = {(V,A)}中的不同分量。
推论23.2
设G=(V,E)是一个在边E上定义了实数值权重函数w的连通无向图。集合A为E的一个子集,且A包括在图G的某棵最小生成树中,
并设C=(Vc,Ec)为森林GA = {(V,A)}中的一个连通分量(树)。如果边(u,v)是连接C和GA中某个其他联通分脸的一条轻量级边,则边(u,v)对于集合A是安全的。
Kruskal算法和Prim算法
这两种算法都是前一节所讨论的通用算法的细化,每种算法都使用一条具体的规则来确定GENERIC-MST算法第三行所描述的安全边。
Kruskal算法:集合A是一个森林,其结点就是给定图的结点,每次加入集合A的安全边永远是权重最小的连接两个不同分量的边。
Prim算法:集合A是一棵树,每次加入A的安全边永远是连接A和A之外某个结点的边中权重最小的边。
Kruskal算法:
使用一个不相交集合数据结构来维护几个互不相交的元素集合,每个集合代表当前森林中的一棵树。可以看出,该算法的实现与21.1节所讨论的计算图的连通分量算法类似。操作FIND-SET(u)用于返回包含元素u的集合的代表元素。操作UNION可看作对两棵树进行合并。每次选择权重最小的边加入到森林中,因此,Kruskal算法显然属于贪心算法。
时间复杂度分析:
Prim算法:
Prim算法的工作原理与Dijkstra的最短路径算法相似。对于两者的不同之处,我之前也写过一篇博客展示自己的理解,希望对你有所帮助:https://blog.csdn.net/dutmathjc/article/details/105888831