Kruskal 算法(最小生成树)

最小生成树 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,因此当作常数也可,这里不再深究。

你可能感兴趣的:(kruskal)