算法导论随笔(十):最小生成树(MST)与Kruskal算法、Prim算法

这篇文章中我来写写图的最小生成树,以及计算一个图的最小生成树的算法,即Kruskal算法和Prim算法。两个算法均使用了贪婪策略

1. 最小生成树

先来谈谈 最小生成树(Minimum Spanning Tree,MST) 的概念。这个概念分为三个部分:最小,生成,和树。因此,这里分别对这三个概念作出解释。首先,树的定义在算法导论随笔(四):树形结构与二叉树已经有完整介绍。这里,介绍生成树(Spanning tree)的概念。
首先,当我们以 “生成” 作为前缀来修饰一个图或一棵树时,表示的是该树和该图是依赖一个图产生的。对于一个图G来说,它的 “生成”子图P或“生成”树T 指的是,该子图P或树T 必须包含G中的所有顶点 ,而不必包含G中所有的边。

因此,对于生成子图和生成树,可以进行如下定义:
若一个图G的子图P包含了所有G中的顶点,则P是G的生成子图。
若G的生成子图P本身是一棵树,则P是G的生成树。
在这里插入图片描述
例如对于上面的图G,有下面三个生成树。
在这里插入图片描述
如果树T是图G的生成树,而且T中包含的边的权重之和小于等于图G的任意其他生成树,则称树T为图G的最小生成树。

例如下图中,所有顶点和所有边(包括红色边和蓝色虚线边)构成了一个图。而图中所有顶点和所有红色边构成了一棵树,这棵树就是这个图的最小生成树
可以看出,最小生成树的实质就是用图中权重最小的边连接所有的顶点,并且保证图的连通性。当然,由于是树,因此也一定不能有循环路径(cycle)存在。
在这里插入图片描述
最小生成树的应用范围非常广泛,比如通讯网络、交通网络等等。

2. 最小生成树的计算:Kruskal算法

Kruskal算法使用了贪心策略来计算最小生成树。先来看算法的伪代码。
在这里插入图片描述
算法的输入是一个连通图G,该图有n个顶点和m条边
算法输出一个图G的最小生成树

这个算法先为G中的每一个顶点v创建一个集群,用通俗的方式就是,为每一个顶点创造一个 “群聊”,该群聊初始时只有一个顶点。

接下来用一个优先级队列Q存储图G中的所有边,排序方式是按照边的权重从小到大排列
接着初始化一个空树T,作为算法的结果。

由于最小生成树T的目的是以最小权重和保证树的连通性,而越少的边就代表着越少的权重和。对于n个顶点的图,只需要保证该树T中有n-1条边,即可连接所有顶点。(5跟手指只有4个指缝)

因此,当树T中有少于n-1条边时:
从Q中取出权值最小的边,记作(u, v)。若顶点u和顶点v不处于同一个“群聊”中,则把边(u, v)存入树T中,并且把顶点u和顶点v的两个群聊合并为一个,也就是说,新群聊中有u和v两个顶点。
重复上述操作直至树T不少于n-1条边。然后树T即是图G的最小生成树。

来看下面的例子。图中共有9个顶点,因此最小生成树只需要有8条边。注意看每一步中取出的边的权值。
第一步:首先取出权重最小,即权值为1的边(h, g)。由于h和g分属于不同的群聊,因此把它们合并为到一个群中。新群为(h, g)。将(h, g)加入T中。T = {(h, g)}。如下图。
在这里插入图片描述
第二步:取出上图中尚未取出的边中拥有最小权值的边,即权值为2的(i, c),把它们合并为到一个群中。新群为(i, c)。T = {(h, g), (i, c)}。如下图。
在这里插入图片描述
第三步:取出上图中尚未取出的边中拥有最小权值的边,即权值为2的(g, f)。把(f)和(g, h)合并为到一个群中。新群为(h, g, f)。T = {(h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第四步:取出上图中尚未取出的边中拥有最小权值的边,即权值为4的(a, b)。把它们合并为到一个群中。新群为(a, b)。T = {(a, b), (h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第五步::取出上图中尚未取出的边中拥有最小权值的边,即权值为4的(c, f)。把(i, c)和(h, g, f)合并为到一个群中。新群为(i, c, h, g, f)。T = {(c, f), (a, b), (h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第六步:取出上图中尚未取出的边中拥有最小权值的边,即权值为6的(i, g)。i和g处于同一个群(i, c, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
第七步:取出上图中尚未取出的边中拥有最小权值的边,即权值为7的(c, d)。把d和合并到c的群中。新群为(i, c, d, h, g, f)。T = {(c, d), (c, f), (a, b), (h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第八步:取出上图中尚未取出的边中拥有最小权值的边,即权值为7的(i, h)。i和h处于同一个群(i, c, d, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
第九步:取出上图中尚未取出的边中拥有最小权值的边,即权值为8的(a, h)。把(a, b)和(i, c, d, h, g, f)合并。新群为(a, b, i, c, d, h, g, f)。T = {(a, h), (c, d), (c, f), (a, b), (h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第十步:取出上图中尚未取出的边中拥有最小权值的边,即权值为8的(b, c)。它们处于同一个群(a, b, i, c, d, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
第十一步:取出上图中尚未取出的边中拥有最小权值的边,即权值为9的(d, e)。把(e)和(a, b, i, c, d, h, g, f)合并。新群为(a, b, e, i, c, d, h, g, f)。T = {(d, e), (a, h), (c, d), (c, f), (a, b), (h, g), (i, c), (g, f)}。如下图。
在这里插入图片描述
第十二步:取出上图中尚未取出的边中拥有最小权值的边,即权值为10的(f, e)。它们处于同一个群(a, b, e, i, c, d, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
第十三步:取出上图中尚未取出的边中拥有最小权值的边,即权值为11的(b, h)。它们处于同一个群(a, b, e, i, c, d, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
第十四步:取出上图中尚未取出的边中拥有最小权值的边,即权值为14的(d, f)。它们处于同一个群(a, b, e, i, c, d, h, g, f)中,因此不做任何处理。如下图。
在这里插入图片描述
此时T = {(d, e), (a, h), (c, d), (c, f), (a, b), (h, g), (i, c), (g, f)} 已经有了8条边,因此算法结束,T即为该图的最小生成树。也就是上图中用深黑色描出来的边。

该算法的复杂度为
T ( n ) = O ( E l o g V ) T(n) = O(ElogV)
其中E代表图G中边的数量,V代表G中顶点的数量。

3. 最小生成树的计算:Prim算法

下面来简单谈谈计算最小生成树的另一个算法:Prim算法。该算法同样使用了贪婪策略。算法的中心思想是:首先从图G中任意一个顶点开始。假设这个顶点叫A。将A加入一个群。然后比较群外所有与这个群中的顶点相邻的边,将权重最小的边的另外一个端点加入群中。然后重复上述操作直到所有顶点都已被加入到群中。

我们来看下面的例子。假设我们有下面的一个图 ,算法从顶点a开始。
在这里插入图片描述
将顶点a加入群中。上图中群外与群内任意一点连接的边有(a, b)和(a, h),其中(a, b)的权值最小,因此把b加入群中。如下图。
在这里插入图片描述
接下来,上图中与群中的顶点连接的边为(b, c)和(a, h),两个边的权值都为8。这里我们随机选择(b, c)这条边,把c加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为2的(c, i)。将i加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为4的(c, f)。将f加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为2的(f, g)。将g加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为1的(g, h)。将h加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为7的(c, d)。将d加入群中。如下图。
在这里插入图片描述
接下来,上图中与群内顶点连接的权重最小的边为权值为9的(d, e)。将e加入群中。如下图。
在这里插入图片描述
此时群中已经包含所有顶点。算法结束。这个群就是最小生成树,也就是上图中用深黑色描出来的边。

算法的伪代码如下。
在这里插入图片描述
算法的复杂度t同样为
T ( n ) = O ( E l o g V ) T(n) = O(ElogV)

4. 写在最后

本篇文章中讨论了最小生成树的两种算法:Kruskal算法和Prim算法。两种算法均使用贪心策略。后面的文章中,我会介绍更多的图算法,例如同样使用贪心策略求最大流最小切的Ford-Fulkerson算法等。