定义

在阅读下列内容之前,请务必阅读 图论相关概念 与 树基础 部分,并了解以下定义:

  1. 生成子图
  2. 生成树

我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。

注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。

Kruskal 算法

Kruskal 算法是一种常见并且好写的最小生成树算法,由 Kruskal 发明。该算法的基本思想是从小到大加入边,是个贪心算法。

前置知识

并查集、贪心、图的存储。

实现

图示:

伪代码:

算法虽简单,但需要相应的数据结构来支持……具体来说,维护一个森林,查询两个结点是否在同一棵树中,连接两棵树。

抽象一点地说,维护一堆 集合,查询两个元素是否属于同一集合,合并两个集合。

其中,查询两点是否连通和连接两点可以使用并查集维护。

如果使用 的排序算法,并且使用 或 的并查集,就可以得到时间复杂度为 的 Kruskal 算法。

证明

思路很简单,为了造出一棵最小生成树,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了 条边,即形成了一棵树。

证明:使用归纳法,证明任何时候 K 算法选择的边集都被某棵 MST 所包含。

基础:对于算法刚开始时,显然成立(最小生成树存在)。

归纳:假设某时刻成立,当前边集为 ,令 为这棵 MST,考虑下一条加入的边 。

如果 属于 ,那么成立。

否则, 一定存在一个环,考虑这个环上不属于 的另一条边 (至少存在一条)。

首先, 的权值一定不会比 小,不然 会在 之前被选取。

然后, 的权值一定不会比 大,不然 就是一棵比 还优的生成树了。

所以, 包含了 ,并且也是一棵最小生成树,归纳成立。

例题

有 朵云,你要将它们连成 个棉花糖,将 云朵和 连接起来需要 的代价,求最小代价。

Prim 算法

Prim 算法是另一种常见并且好写的最小生成树算法。该算法的基本思想是从一个结点开始,不断加点(而不是 Kruskal 算法的加边)。

实现

图示:

具体来说,每次要选择距离最小的一个结点,以及用新的边更新其他结点的距离。

其实跟 Dijkstra 算法一样,每次找到距离最小的一个点,可以暴力找也可以用堆维护。

堆优化的方式类似 Dijkstra 的堆优化,但如果使用二叉堆等不支持 decrease-key 的堆,复杂度就不优于 Kruskal,常数也比 Kruskal 大。所以,一般情况下都使用 Kruskal 算法,在稠密图尤其是完全图上,暴力 Prim 的复杂度比 Kruskal 优,但 不一定 实际跑得更快。

暴力:。

二叉堆:。

Fib 堆:。

伪代码:

注意:上述代码只是求出了最小生成树的权值,如果要输出方案还需要记录每个点的 代表的是哪条边。

证明

从任意一个结点开始,将结点分成两类:已加入的,未加入的。

每次从未加入的结点中,找一个与已加入的结点之间边权最小值最小的结点。

然后将这个结点加入,并连上那条边权最小的边。

重复 次即可。

证明:还是说明在每一步,都存在一棵最小生成树包含已选边集。

基础:只有一个结点的时候,显然成立。

归纳:如果某一步成立,当前边集为 ,属于 这棵 MST,接下来要加入边 。

如果 属于 ,那么成立。

否则考虑 中环上另一条可以加入当前边集的边 。

首先, 的权值一定不小于 的权值,否则就会选择 而不是 了。

然后, 的权值一定不大于 的权值,否则 就是一棵更小的生成树了。

因此, 和 的权值相等, 也是一棵最小生成树,且包含了 。

Boruvka 算法

接下来介绍另一种求解最小生成树的算法——Boruvka 算法。该算法的思想是前两种算法的结合。它可以用于求解无向图的最小生成森林。(无向连通图就是最小生成树。)

在边具有较多特殊性质的问题中,Boruvka 算法具有优势。例如 CF888G 的完全图问题。

为了描述该算法,我们需要引入一些定义:

  1. 定义 为我们当前找到的最小生成森林的边。在算法执行过程中,我们逐步向 加边,定义 连通块 表示一个点集 ,且这个点集中的任意两个点 , 在 中的边构成的子图上是连通的(互相可达)。
  2. 定义一个连通块的 最小边 为它连向其它连通块的边中权值最小的那一条。

初始时,,每个点各自是一个连通块:

  1. 计算每个点分别属于哪个连通块。将每个连通块都设为「没有最小边」。
  2. 遍历每条边 ,如果 和 不在同一个连通块,就用这条边的边权分别更新 和 所在连通块的最小边。
  3. 如果所有连通块都没有最小边,退出程序,此时的 就是原图最小生成森林的边集。否则,将每个有最小边的连通块的最小边加入 ,返回第一步。

下面通过一张动态图来举一个例子(图源自 维基百科):

eg

当原图连通时,每次迭代连通块数量至少减半,算法只会迭代不超过 次,而原图不连通时相当于多个子问题,因此算法复杂度是 的。给出算法的伪代码:(修改自 维基百科)

需要注意边与边的比较通常需要第二关键字(例如按编号排序),以便当边权相同时分出边的大小。

习题

最小生成树的唯一性

考虑最小生成树的唯一性。如果一条边 不在最小生成树的边集中,并且可以替换与其 权值相同、并且在最小生成树边集 的另一条边。那么,这个最小生成树就是不唯一的。

对于 Kruskal 算法,只要计算为当前权值的边可以放几条,实际放了几条,如果这两个值不一样,那么就说明这几条边与之前的边产生了一个环(这个环中至少有两条当前权值的边,否则根据并查集,这条边是不能放的),即最小生成树不唯一。

寻找权值与当前边相同的边,我们只需要记录头尾指针,用单调队列即可在 (m 为边数)的时间复杂度里优秀解决这个问题(基本与原算法时间相同)。

次小生成树

非严格次小生成树

定义

在无向图中,边权和最小的满足边权和 大于等于 最小生成树边权和的生成树

求解方法

  • 求出无向图的最小生成树 ,设其权值和为
  • 遍历每条未被选中的边 ,找到 中 到 路径上边权最大的一条边 ,则在 中以 替换 ,可得一棵权值和为 的生成树 .
  • 对所有替换得到的答案 取最小值即可

如何求 路径上的边权最大值呢?

我们可以使用倍增来维护,预处理出每个节点的 级祖先及到达其 级祖先路径上最大的边权,这样在倍增求 LCA 的过程中可以直接求得。

严格次小生成树

定义

在无向图中,边权和最小的满足边权和 严格大于 最小生成树边权和的生成树

求解方法

考虑刚才的非严格次小生成树求解过程,为什么求得的解是非严格的?

因为最小生成树保证生成树中 到 路径上的边权最大值一定 不大于 其他从 到 路径的边权最大值。换言之,当我们用于替换的边的权值与原生成树中被替换边的权值相等时,得到的次小生成树是非严格的。

解决的办法很自然:我们维护到 级祖先路径上的最大边权的同时维护 严格次大边权,当用于替换的边的权值与原生成树中路径最大边权相等时,我们用严格次大值来替换即可。

这个过程可以用倍增求解,复杂度 。

瓶颈生成树

定义

无向图 的瓶颈生成树是这样的一个生成树,它的最大的边权值在 的所有生成树中最小。

性质

最小生成树是瓶颈生成树的充分不必要条件。 即最小生成树一定是瓶颈生成树,而瓶颈生成树不一定是最小生成树。

关于最小生成树一定是瓶颈生成树这一命题,可以运用反证法证明:我们设最小生成树中的最大边权为 ,如果最小生成树不是瓶颈生成树的话,则瓶颈生成树的所有边权都小于 ,我们只需删去原最小生成树中的最长边,用瓶颈生成树中的一条边来连接删去边后形成的两棵树,得到的新生成树一定比原最小生成树的权值和还要小,这样就产生了矛盾。

例题

POJ 2395 Out of Hay

给出 n 个农场和 m 条边,农场按 1 到 n 编号,现在有一人要从编号为 1 的农场出发到其他的农场去,求在这途中他最多需要携带的水的重量,注意他每到达一个农场,可以对水进行补给,且要使总共的路径长度最小。 题目要求的就是瓶颈树的最大边,可以通过求最小生成树来解决。

最小瓶颈路

定义

无向图 中 x 到 y 的最小瓶颈路是这样的一类简单路径,满足这条路径上的最大的边权在所有 x 到 y 的简单路径中是最小的。

性质

根据最小生成树定义,x 到 y 的最小瓶颈路上的最大边权等于最小生成树上 x 到 y 路径上的最大边权。虽然最小生成树不唯一,但是每种最小生成树 x 到 y 路径的最大边权相同且为最小值。也就是说,每种最小生成树上的 x 到 y 的路径均为最小瓶颈路。

但是,并不是所有最小瓶颈路都存在一棵最小生成树满足其为树上 x 到 y 的简单路径。

例如下图:

1 到 4 的最小瓶颈路显然有以下两条:1-2-3-4。1-3-4。

但是,1-2 不会出现在任意一种最小生成树上。

应用

由于最小瓶颈路不唯一,一般情况下会询问最小瓶颈路上的最大边权。

也就是说,我们需要求最小生成树链上的 max。

倍增、树剖都可以解决,这里不再展开。

Kruskal 重构树

定义

在跑 Kruskal 的过程中我们会从小到大加入若干条边。现在我们仍然按照这个顺序。

首先新建 个集合,每个集合恰有一个节点,点权为 。

每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权,同时将两个集合的根节点分别设为新建点的左儿子和右儿子。然后我们将两个集合和新建点合并成一个集合。将新建点设为根。

不难发现,在进行 轮之后我们得到了一棵恰有 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子。这棵树就叫 Kruskal 重构树。

举个例子:

这张图的 Kruskal 重构树如下:

性质

不难发现,原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值。

也就是说,到点 的简单路径上最大边权的最小值 的所有点 均在 Kruskal 重构树上的某一棵子树内,且恰好为该子树的所有叶子节点。

我们在 Kruskal 重构树上找到 到根的路径上权值 的最浅的节点。显然这就是所有满足条件的节点所在的子树的根节点。

如果需要求原图中两个点之间的所有简单路径上最小边权的最大值,则在跑 Kruskal 的过程中按边权大到小的顺序加边。