对于一个带权(假设每条边上的权均为大于零的实数)连通无向图 G 中的不同生成树,其每棵树的所有边上的权值之和也可能不同;图的所有生成树中具有边上的权值之和最小的树称为图的最小生成树(Minimal Spanning Tree)。
按照生成树的定义, n n n 个顶点的连通图的生成树有 n n n 个顶点、 ( n − 1 ) (n-1) (n−1) 条边。因此,构造最小生成树的准则有以下 3 条:
- 必须只使用该图中的边来构造最小生成树;
- 必须使用且仅使用 ( n − 1 ) (n-1) (n−1) 条边来连接图中的 n n n 个顶点;
- 不能使用产生回路的边。
构造最小生成树有多种算法,其中多数算法利用了最小生成树的下列一种简称为 MST 的性质:假设 N = ( V , E ) N=(V,E) N=(V,E) 是一个连通网(带权连通图), U U U 是顶点集 V V V 的一个非空子集。若 ( u , v ) (u,v) (u,v) 是一条具有最小权值(代价)的边,其中 $ u\in U,~v\in V-U$ ,则必存在一棵包含边 ( u , v ) (u,v) (u,v) 的最小生成树。
这个结论的证明,此处从略,可以参考《数据结构》(严蔚敏,人民邮电出版社)。
求图的最小生成树有很多实际应用,例如城市之间的交通工程造价最优问题就是一个最小生成树问题。求图的最小生成树有两个算法著名的算法:普里姆算法和克鲁斯卡尔算法。
普里姆算法
普里姆算法的构造过程
普里姆(Prim)算法是一种构造最小生成树的算法。
假设 G = ( V , E ) G=(V,E) G=(V,E) 是一个具有 n n n 个顶点的带权连通图, T = ( U , T E ) T=(U,TE) T=(U,TE) 是 G G G 的最小生成树,其中 U U U 是 T T T 的顶点集, T E TE TE 是 T T T 的边集,则由 G G G 构造从起始点 v v v 出发的最小生成树 T T T 的步骤如下:
- 初始化 U = { v } U=\{v\} U={v} ,以 v v v 到其他顶点的所有边为候选边。
- 重复以下步骤 ( n − 1 ) (n-1) (n−1) 次,使得其他 ( n − 1 ) (n-1) (n−1) 个顶点被加入到 U U U 中。
- 从候选边中挑选权值最小的边加入 T E TE TE ,设该边在 V − U V-U V−U 中的顶点是 k k k ,将 k k k 加入 U U U 中;
- 考査当前 V − U V-U V−U 中的所有顶点 j j j ,修改候选边,若 ( k , i ) (k,i) (k,i) 的权值小于原来和顶点 j j j 关联的候选边,则用 ( k , i ) (k,i) (k,i) 取代后者作为候选边。
例如,对于图 8.4.7 所示带权连通图,假设起始点为顶点 0,采用普里姆算法构造最小生成树。
- 最小生成树 T T T 包含所有顶点。
- U = { 0 } , V − U = { 1 , 2 , 3 , 4 , 5 , 6 } U=\{0\},~V-U=\{1,2,3,4,5,6\} U={0}, V−U={1,2,3,4,5,6} ,在这两个顶点集之间选择第 1 条最小边 ( 0 , 5 ) (0,5) (0,5) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) } TE=\{(0,5)\} TE={(0,5)}。
- U = { 0 , 5 } , V − U = { 1 , 2 , 3 , 4 , 6 } U=\{0,5\},~V-U=\{1,2,3,4,6\} U={0,5}, V−U={1,2,3,4,6} ,在这两个顶点集之间选择第 2 条最小边 ( 5 , 4 ) (5,4) (5,4) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) , ( 5 , 4 ) } TE=\{(0,5),(5,4)\} TE={(0,5),(5,4)}。
- U = { 0 , 5 , 4 } , V − U = { 1 , 2 , 3 , 6 } U=\{0,5,4\},~V-U=\{1,2,3,6\} U={0,5,4}, V−U={1,2,3,6} ,在这两个顶点集之间选择第 3 条最小边 ( 4 , 3 ) (4,3) (4,3) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) , ( 5 , 4 ) , ( 4 , 3 ) } TE=\{(0,5),(5,4),(4,3)\} TE={(0,5),(5,4),(4,3)}。
- U = { 0 , 5 , 4 , 3 } , V − U = { 1 , 2 , 6 } U=\{0,5,4,3\},~V-U=\{1,2,6\} U={0,5,4,3}, V−U={1,2,6} ,在这两个顶点集之间选择第 4 条最小边 ( 3 , 2 ) (3,2) (3,2) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) , ( 5 , 4 ) , ( 4 , 3 ) , ( 3 , 2 ) } TE=\{(0,5),(5,4),(4,3),(3,2)\} TE={(0,5),(5,4),(4,3),(3,2)}。
- U = { 0 , 5 , 4 , 3 , 2 } , V − U = { 1 , 6 } U=\{0,5,4,3,2\},~V-U=\{1,6\} U={0,5,4,3,2}, V−U={1,6} ,在这两个顶点集之间选择第 5 条最小边 ( 2 , 1 ) (2,1) (2,1) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) , ( 5 , 4 ) , ( 4 , 3 ) , ( 3 , 2 ) , ( 2 , 1 ) } TE=\{(0,5),(5,4),(4,3),(3,2),(2,1)\} TE={(0,5),(5,4),(4,3),(3,2),(2,1)}。
- U = { 0 , 5 , 4 , 3 , 2 , 1 } , V − U = { 6 } U=\{0,5,4,3,2,1\},~V-U=\{6\} U={0,5,4,3,2,1}, V−U={6} ,在这两个顶点集之间选择第 6 条最小边 ( 1 , 6 ) (1,6) (1,6) 添加到 T E TE TE 中,即 T E = { ( 0 , 5 ) , ( 5 , 4 ) , ( 4 , 3 ) , ( 3 , 2 ) , ( 2 , 1 ) , ( 1 , 6 ) } TE=\{(0,5),(5,4),(4,3),(3,2),(2,1),(1,6)\} TE={(0,5),(5,4),(4,3),(3,2),(2,1),(1,6)},即得到了最小生成树,如图 8.4.8 所示。
如果每次选择最小边时,存在多条同样权值的边可选,则任选其一即可。
克鲁斯卡尔算法
克鲁斯卡尔(Kruskal)算法是一种按权值的递增次序选择合适的边来构造最小生成树的方法。
克鲁斯卡尔算法的构造过程
假设 G = ( V , E ) G=(V,E) G=(V,E) 是一个具有 n n n 个顶点的带权连通无向图, T = ( U , T E ) T=(U,TE) T=(U,TE) 是 G G G 的最小生成树,则构造最小生成树的步骤如下:
- 置 U U U 的初值为 V V V (即包含有 G G G 中的全部顶点), T E TE TE 的初值为空集(即图 T T T 中的每一个顶点都构成一个分量)。
- 将图 G G G 中的边按权值从小到大的顺序依次选取,若选取的边未使生成树 T T T 形成回路,则加入 T E TE TE ,否则舍弃,直到 T E TE TE 中包含 ( n − 1 ) (n-1) (n−1) 条边为止。
以图 8.4.7 所示的带权连通图为例,采用克鲁斯卡尔算法构造最小生成树,其过程如下:
-
将所有边按权值递增排序:
(0,5), (2,3), (1,6), (1,2), (3,6), (3,4), (4,6), (4,5), (0,1)
。 -
最小生成树 T T T 包含所有顶点。
-
选最小的边
(0,5)
加入到 T T T 中,此时不会出现回路。 -
选第 2 小的边
(2,3)
加入到 T T T 中,此时不出现回路。 -
选第 3 小的边
(1,6)
加入到 T T T 中,此时不出现回路。 -
选第 4 小的边
(1,2)
加入到 T T T 中,此时不出现回路。 -
选第 5 小的边
(3,6)
加入到 T T T 中,此时出现回路,故舍弃它。选第 6 小的边(3,4)
加入到 T T T 中,此时不出现回路。 -
选第 7 小的边
(4,6)
加入到 T T T 中,此时出现回路,故舍弃它。选第 8 小的边(4,5)
加入到 T T T 中,此时不出现回路。至此在 T T T 中已经有 6 条边( n = 7 n=7 n=7),产生了最小生成树,如图 8.4.9 所示。
图 8.4.9 由克鲁斯卡尔算法得到的最小生成树
将这里用克鲁斯卡尔算法所得到的最小生成树,与上一节中用普里姆算法得到的最小生成树对比,可以发现,二者结果相同。这在本例中是一个巧合。