最小生成树 Minimal Spanning Tree(MST)问题
已知一个连通图G={V,E}(G: graph, V: vector, E: edge),求子图G’={V,E’},使得子图里的边权总和最小。
也就是说,G’ 仍然是一个连通图,但是边的数量要删减。
为什么是最小生成树
假设 G’存在环,那么删去这个环里的任意一条边都不影响整个图的连通性,但边权总和会更小。对了,要补充一点:这个连通图没有负环(但不排除存在负边权)。否则,删去负环中的任何一条边反而会使边权总和变大。
原理
Kruskal算法本质上就是一个贪心算法。
1、将G所有条边按权从小到大排序,MST开始为空。
2、从小到大次序取边(u,v)。
3、若加入边(u,v),MST就有环,则放弃此边,转2。
4、将边(u,v)加入MST,如果已经加了E-1条边,结束。否则转2。
实现方法
最容易想到的方法是暴力,但用并查集的实现则更简洁高效。
在算法原理中,最难实现的就是MST是否有环。这个问题事实上可以转化为 u 和 v 是否在同一连通块里面,这样用并查集则巧妙地解决了该问题,只要判断两个顶点的“祖先”是否相同即可。
另外一点,要证明:当 u 与 v 在不同的连通块时,加入边(u,v)一定是最优的。这一点很简单:在之后的边里面,即使能连通 u 与 v,权值也明显会大于(u,v)的权值。
//Kruskal 算法
for (int i=0; i<V; i++) f[i]=i; //并查集初始化
sort(edge, edge+E, cmp); //将边按权重排序
int MST_edge=0, MST_sum_weight; //MST的边的数量、MST的权重总和
for (int i=0; i<E; i++)
{
u = find_set(edge[i].u); //找这两个节点的“祖先”
v = find_set(edge[i].v);
if (u != v)
{
MST_sum_weight += edge[i].weight;
union_set(u, v); //合并两个连通块
MST_edge++;
if (MST_edge == E-1) break; //可省,对时间复杂度影响不大
}
}
时间复杂度
时间复杂度大约为 O(|E|log2|E|+α|V|) ,α是阿克曼函数的一个增长速度非常缓慢的反函数,在实际运算中不会超过5,因此当作常数也可,这里不再深究。