最小生成树
我们定义无向连通图的 最小生成树(Minimum Spanning Tree,MST)为边权和最小的生成树。
注意:只有连通图才有生成树,而对于非连通图,只存在生成森林。
Kruskal算法
Kruskal是一个贪心算法,我们从最小边权的边开始,按边权从小到大依次加入,如果某次加边产生了环,就扔掉这条边,直到加入了条边,即得到了最小生成树。
正确性证明使用归纳法(proof by induction)。证明任何时候算法选择的边集都被某棵 MST 所包含。
基础:对于算法刚开始时,显然成立(最小生成树存在)。
归纳:假设某时刻成立,当前边集为 ,令 为这棵 MST,考虑下一条加入的边 。
如果 属于 ,那么成立。
否则, 一定存在一个环,考虑这个环上不属于 的另一条边 (一定只有一条)。
首先, 的权值一定不会比 小,不然 会在 之前被选取。
然后, 的权值一定不会比 大,不然 就是一棵比 还优的生成树了。
所以, 包含了 ,并且也是一棵最小生成树,归纳成立。
实现(使用并查集):
#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 | 聪明的猴子 | 普及/提高- | ||||
P2330 | SCOI | 繁忙的都市 | 普及/提高- | |||
P1991 | 无线通讯网 | 普及/提高- | ||||
P4047 | JSOI | 部落划分 | 2010 | 普及+/提高 | ||
P8191 | USACO Gold | Moo Network | 2022 | 普及+/提高 | 解答 | |
UVA1395 | UVA | 苗条的生成树 Slim Span | 提高+/省选- | |||
P2323 | HNOI | 公路修建问题 | 提高+/省选- |
次小生成树和严格次小生成树
习题 | ||||||
---|---|---|---|---|---|---|
P4180 | 严格次小生成树 | 省选/NOI- |
最小生成树的唯一性
考虑最小生成树的唯一性。如果一条边 不在最小生成树的边集中,并且可以替换与其 权值相同、并且在最小生成树边集 的另一条边。那么,这个最小生成树就是不唯一的。
对于 Kruskal 算法,只要计算为当前权值的边可以放几条,实际放了几条,如果这两个值不一样,那么就说明这几条边与之前的边产生了一个环(这个环中至少有两条当前权值的边,否则根据并查集,这条边是不能放的),即最小生成树不唯一。
寻找权值与当前边相同的边,我们只需要记录头尾指针,用单调队列即可在 (m 为边数)的时间复杂度里优秀解决这个问题(基本与原算法时间相同)。
习题 | ||||||
---|---|---|---|---|---|---|
POJ1679 | POJ | The Unique MST |