一、基本概念
注意:我们讨论的最小生成树问题是对于连通无向图的。
生成树:设连通无向图边集的一个子集,若是无环的,并且连通所有的结点(此时一定为一棵树),则称为的生成树。
最小生成树:图生成树中,权重最小的。
切割:无向图的一个切割是点集的一个划分,如图一所示。
横跨:如果一条边的一个端点位于集合另一个端点位于集合,则称该条边横跨切割。
尊重:如果集合中不存在横跨该切割的边,则称该切割尊重集合。
轻量级边:在横跨一个切割的所有边中,权重最小的边称为轻量级边。
安全边:设边集合是图的某棵最小生成树的一个子集,若将边加入到集合中使得也是某棵最小生成树的子集,则称边为的安全边。
生成最小生成树的基本框架是:初始化,然后不断向中加入安全边直到生成最小生成树。(不同的最小生成树算法选择安全边的策略有所不同。)伪代码如下:
定理:设是一个在边上定义了实数值权重函数的连通无向图。设集合为的一个子集,且包括在图的某棵最小生成树中,设是图中尊重集合的任意一个切割,又设是横跨切割的一条轻量级边。那么边对于集合是安全的。
该定理可以更好地理解GENERIC-MST算法:随着算法的运行,集合总是保持无环状态。在任意时刻,图是一个森林,中每个连通分量则是一棵树。重要的是,每次添加的安全边所连接的是中不同的连通分量(否则产生环)。该算法while循环共执行次,初始时,,中有棵树(即每个结点是一棵树),每次循环树的数量将减少一棵直到仅剩一棵树时,算法终止,该树就是图的一棵最小生成树。
重要的是,由该定理引出的如下推论将是我们得出最小生成树算法的直接理论依据。
推论:设是一个在边上定义了实数值权重函数的连通无向图。设集合为的一个子集,且包括在图的某棵最小生成树中,并设为森林中的一个连通分量(树)。如果边是连接和中某个其他连通分量的一条轻量级边,则边对于集合是安全的。
二、Kruskal算法
算法介绍:在Kruskal算法中,集合是一个森林,每次加入到中的安全边是连接两个不同分量的边中权重最小的。初始每个结点均为一个连通分量,算法的过程是不断找到连接不同连通分量中最小的边,则由推论可保证正确性。等价的实现过程是,首先按权值大小对边进行排序,按从小到大的顺序检查边,如果连接不同连通分量则保留,如果在同一连通分量中则忽略。
伪代码:
解释:我们使用不相交集合数据结构实现不同连通分量的表示。初始时,MAKE-SET为每个结点创造一个集合。FIND-SET检查和是否属于同一连通分量(不相交集合),若不属于,则UNION合并和所在连通分量。
时间复杂度:。
复杂度分析:运行时间依赖于不相交集合数据结构的实现,我们使用不相交集合森林实现,并运用按秩合并和路径压缩两个策略,这时迄今为止渐进时间最快的实现方式。第4行排序算法最快。2至8行一共执行次FIND-SET和UNION操作和次MAKE-SET操作,则运行时间为。则总运行时间为,也即()。
举例:
三、Prim算法
算法介绍:在Prim算法中,集合是一棵树,每次加入到中的安全边是连接和之外某个结点中的最小边,初始时中包含任意一个结点。算法实现过程是,将不在中的结点都存放在一个基于属性的最小优先队列。保存的是结点连接树的所有边的最小权重。保存的是在树中的父结点。
伪代码:
解释:为我们任意选择的根结点,将外的结点值初始化为,初始化为NIL,设置为0以便我们第一个选择。最小优先队列初始化为所有结点。
时间复杂度:依赖于最小优先队列的实现:
(1)一般数组:。
(2)普通二叉堆:。
(3) 斐波那契堆:。
复杂度分析:1至4行初始化共需要时间,11行值更新后隐含DECREASE-KEY操作。对于第9行判断结点是否在中,我们可以为每个结点添加一个标记,在中为True,不在则为False,当结点从中删除时,该标记由True变为False,于是判断操作为常数时间。算法共需执行一次建堆BUILD-MIN-HEAP,次EXTRACT-MIN,次DECREASE-KEY。
(1)一般数组:EXTRACT-MIN为,DECREASE-KEY为,运行时间为,也即。
(2)普通二叉堆:EXTRACT-MIN为,DECREASE-KEY为,运行时间为,也即。
(3)斐波那契堆:EXTRACT-MIN为,DECREASE-KEY为,运行时间为。
举例:
四、Boruvka算法
算法介绍:Boruvka算法是最早的计算MST的算法,它的过程类似于Kruskal和Prim的结合,其用一句话总结就是:从当前每个连通块开始向外找到连接其他连通块的最小边,直到只剩一个连通块。Boruvka算法最大的一个特点是,该算法允许并行化运行,而以上两个算法本质上都是连续的。
伪代码:
解释:图存储图的所有顶点及发现的最小生成树中的边。用来记录当前连通块的个数。循环检查,当其降为1说明最小生成树已经生成。函数COUNTANDLABEL完成两件工作,不仅计算当前图的连通块个数,还为每个结点分配其所属连通块。显然COUNTANDLABEL可用DFS或BFS运行搜索一次完成。
函数ADDALLSAFEEDGES用来合并连通块。用来记录当前第个连通块连接其它连通块权值最小的边,所以数组每次只有前个记录有意义。返回结点所属的连通块标号,其值即为DFS或BFS过程中生成的森林中的第几棵树。ADDALLSAFEEDGES每次执行都遍历一遍图的所有边集。如果边连接的是同一个连通块的两个结点,则该边已经在中生成或者不在最终的最小生成树中,不考虑即可。如果边连接的是不同连通块的两个结点,则检查是否可以更新数组。遍历结束后,将新生成的边加入到图。
时间复杂度:邻接表:;邻接矩阵:。
复杂度分析:我们首先分析while循环次数。每次连通块合并,至少使连通块减半,初始连通块数为(每个顶点即一个连通块),所以循环次数为。ADDALLSAFEEDGES函数显然为,COUNTANDLABEL则为。故复杂度为,即为。
举例(只进行了两次循环):