数据结构:最小生成树的普里姆算法和克鲁斯卡尔算法

对于一个带权(假设每条边上的权均为大于零的实数)连通无向图 G 中的不同生成树,其每棵树的所有边上的权值之和也可能不同;图的所有生成树中具有边上的权值之和最小的树称为图的最小生成树(Minimal Spanning Tree)。

按照生成树的定义, n n n 个顶点的连通图的生成树有 n n n 个顶点、 ( n − 1 ) (n-1) (n−1) 条边。因此,构造最小生成树的准则有以下 3 条:

  1. 必须只使用该图中的边来构造最小生成树;
  2. 必须使用且仅使用 ( n − 1 ) (n-1) (n−1) 条边来连接图中的 n n n 个顶点;
  3. 不能使用产生回路的边。

构造最小生成树有多种算法,其中多数算法利用了最小生成树的下列一种简称为 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 的步骤如下:

  1. 初始化 U = { v } U=\{v\} U={v} ,以 v v v 到其他顶点的所有边为候选边。
  2. 重复以下步骤 ( 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,采用普里姆算法构造最小生成树。


图 8.4.7 带权连通图

  1. 最小生成树 T T T 包含所有顶点。
  2. 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)}。
  3. 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)}。
  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)}。
  5. 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)}。
  6. 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)}。
  7. 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 所示。


图 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 的最小生成树,则构造最小生成树的步骤如下:

  1. 置 U U U 的初值为 V V V (即包含有 G G G 中的全部顶点), T E TE TE 的初值为空集(即图 T T T 中的每一个顶点都构成一个分量)。
  2. 将图 G G G 中的边按权值从小到大的顺序依次选取,若选取的边未使生成树 T T T 形成回路,则加入 T E TE TE ,否则舍弃,直到 T E TE TE 中包含 ( n − 1 ) (n-1) (n−1) 条边为止。

以图 8.4.7 所示的带权连通图为例,采用克鲁斯卡尔算法构造最小生成树,其过程如下:

  1. 将所有边按权值递增排序:(0,5), (2,3), (1,6), (1,2), (3,6), (3,4), (4,6), (4,5), (0,1)

  2. 最小生成树 T T T 包含所有顶点。

  3. 选最小的边 (0,5) 加入到 T T T 中,此时不会出现回路。

  4. 选第 2 小的边 (2,3) 加入到 T T T 中,此时不出现回路。

  5. 选第 3 小的边 (1,6) 加入到 T T T 中,此时不出现回路。

  6. 选第 4 小的边 (1,2) 加入到 T T T​ 中,此时不出现回路。

  7. 选第 5 小的边 (3,6) 加入到 T T T 中,此时出现回路,故舍弃它。选第 6 小的边 (3,4) 加入到 T T T 中,此时不出现回路。

  8. 选第 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 由克鲁斯卡尔算法得到的最小生成树

将这里用克鲁斯卡尔算法所得到的最小生成树,与上一节中用普里姆算法得到的最小生成树对比,可以发现,二者结果相同。这在本例中是一个巧合。

相关推荐
程序员爱钓鱼8 分钟前
循环语句:for、range -《Go语言实战指南》
java·数据结构·算法
矿渣渣11 分钟前
YAFFS2 文件系统的 `yaffs_dev` 数据结构详解
数据结构·文件系统·nand flash·yaffs2
LabVIEW开发1 小时前
LabVIEW中算法开发的系统化解决方案与优化
算法·labview
Phoebe鑫1 小时前
数据结构每日一题day17(链表)★★★★★
数据结构·链表
chenyuhao20241 小时前
链表面试题7之相交链表
数据结构·算法·链表·面试·c#
Pluchon1 小时前
硅基计划2.0 学习总结 壹 Java初阶
java·开发语言·学习·算法
PassLink_1 小时前
[计算机科学#14]:数据结构
数据结构·计算机科学·计算机发展史
仙人掌_lz1 小时前
理解多智能体深度确定性策略梯度MADDPG算法:基于python从零实现
python·算法·强化学习·策略梯度·rl
wjm0410062 小时前
B树和B+树
数据结构·b树
PXM的算法星球2 小时前
一文了解B+树的删除原理
数据结构·b树