最小生成树

最小生成树

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

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

Kruskal算法

Kruskal是一个贪心算法,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了n1n-1条边,即得到了最小生成树。

动画演示

正确性证明使用归纳法(proof by induction)。证明任何时候算法选择的边集都被某棵 MST 所包含。

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

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

如果 ee 属于 TT,那么成立。

否则,T+eT+e 一定存在一个环,考虑这个环上不属于 FF 的另一条边 ff(一定只有一条)。

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

然后,ff 的权值一定不会比 ee 大,不然 T+efT+e-f 就是一棵比 TT 还优的生成树了。

所以,T+efT+e-f 包含了 FF,并且也是一棵最小生成树,归纳成立。

实现(使用并查集):

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
 
//Kruskal最小生成树模板题
int n, m, father[100000];
 
struct node{
    int src, dst;
    int val;
};
 
bool cmp(node a, node b){return a.val < b.val;}
 
int findFather(int a){
    int t = a;
    while(a != father[a])a = father[a]; //找到根节点
    return a;
}
 
void init(){
    for(int i = 1; i <= n; i++)father[i] = i;
}
 
int unionSet(int x, int y){ //和并查集一样,如果将根合并
    int fx = findFather(x);
    int fy = findFather(y);
    if(fx != fy){
        father[fx] = fy;
        return  1;
    }return 0;
}
 
int main(){
    while(cin>>n){
        if(n == 0)break;
        init(); //初始化不能丢
        cin>>m;
        int ans = 0;
        vector<node> pool(m);
        for(int i = 0; i < m; i++){
            cin>>pool[i].src>>pool[i].dst>>pool[i].val;
        }
        
        sort(pool.begin(), pool.end(), cmp);
        int k = 0, i = 0;
        while(k < n - 1){ 
            if(unionSet(pool[i].src, pool[i].dst)){ //如果两个节点的根不一样则合并,本质上将两个预给定的边选择出来
                //也不会成环
                k++;
                ans += pool[i].val;
            }
            i++;
        }
        cout<<ans<<endl;
    }
    return 0;
}
习题
P3366【模板】最小生成树普及-
P2330繁忙的都市普及/提高-
P2504聪明的猴子普及/提高-
P2330SCOI繁忙的都市普及/提高-
P1991无线通讯网普及/提高-
P4047JSOI部落划分2010普及+/提高
P8191USACO GoldMoo Network2022普及+/提高解答
UVA1395UVA苗条的生成树 Slim Span提高+/省选-
P2323HNOI公路修建问题提高+/省选-

次小生成树和严格次小生成树

OI-Wiki

习题
P4180严格次小生成树省选/NOI-

最小生成树的唯一性

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

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

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

习题
POJ1679POJThe Unique MST