最小生成树

定义

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

  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 优,但 不一定 实际跑得更快。

暴力: O(n2+m)O(n^2+m)

二叉堆: O(n+m)lognO(n+m)\log n

Fib 堆: O(nlogn+m)O(n\log n+m)

伪代码:

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

Last updated

Was this helpful?