Kruskal
最常用且大部分情况下效率最高的最小生成树算法。
将边按长度从小到大排序,每次贪心选择最短的一条边,若这条边连接的两个点还为连通,则将两个点所在的连通块用这条边连接,连通块用并查集维护。
证明: 使用归纳法,证明任何时候 Kruskal 算法选择的边集都被某棵 MST 所包含。 基础: 对于算法刚开始时,显然成立(最小生成树存在)。 归纳: 假设某时刻成立,当前边集为 \(F\),令 \(T\) 为这棵 MST,考虑下一条加入的边 \(e\)。 如果 \(e\) 属于 \(T\),那么成立。 否则,\(T+e\) 一定存在一个环,考虑这个环上不属于 \(F\) 的另一条边 \(f\)(一定只有一条)。 首先,\(f\) 的权值一定不会比 \(e\) 小,不然 \(f\) 会在 \(e\) 之前被选取。 然后,\(f\) 的权值一定不会比 \(e\) 大,不然 \(T+e-f\) 就是一棵比 \(T\) 还优的生成树了。 所以,\(T+e-f\) 包含了 \(F\),并且也是一棵最小生成树,归纳成立。
时间复杂度 \(\Theta(m\log m)\)。
Kruskal 重构树
这是 Kruskal 算法除了求最小生成树以外的一个重要应用。
在使用 \(\rm kruskal\) 最小生成树算法的时候会从小到大加入 \(n-1\) 条边。现在仍按照这个顺序,每次加入一条边时将其变成一个点权等于边权的点,然后把它作为所连接两个连通块的父亲节点。
不难发现两个结点在 MST 上的最短路径上的边权最大值为它们在 \(\rm kruskal\) 重构树上 \(\rm LCA\) 的点权,并且从一个点 \(u\) 出发只经过边权小于某值的边能够到达的点集为 \(\rm Kruskal\) 重构树上某点的子树内所有的叶子结点。
Prim
在稠密图上复杂度最优的最小生成树算法。
与 Dijkstra 算法类似,选择一个点作为起点,每次选择到当前已选的点集中任意一个点距离最短的未选择的点并向其连边,再递归操作。
证明: 还是说明在每一步,都存在一棵最小生成树包含已选边集。 基础: 只有一个结点的时候,显然成立。 归纳: 如果某一步成立,当前边集为 \(F\),属于 \(T\) 这棵 MST,接下来要加入边 \(e\)。 如果 \(e\) 属于 \(T\),那么成立。 否则考虑 \(T+e\) 中环上另一条可以加入当前边集的边 \(f\)。 首先,\(f\) 的权值一定不小于 \(e\) 的权值,否则就会选择 \(f\) 而不是 \(e\) 了。 然后,\(f\) 的权值一定不大于 \(e\) 的权值,否则 \(T+e-f\) 就是一棵更小的生成树了。 因此,\(e\) 和 \(f\) 的权值相等,\(T+e-f\) 也是一棵最小生成树,且包含了 \(F\)。
时间复杂度 \(\Theta[(n+m)\log n]\)。
Boruvka
在大多数完全图上求最小生成树所使用的算法。
- 每次遍历所有的点,求出它当前属于哪个连通块。
- 遍历所有的边 \(u-v\),如果 \(u\) 和 \(v\) 不在同一个连通块,就分别用 \(u\) 和 \(v\) 去更新它们所在连通块的最小出边。
- 将每个连通块的最小出边加入 MST 的边集。
由于每次连通块数量减半,所以时间复杂度 \(\Theta(m\log n)\)。
