给定一张带权无向图 G=(V,E),n = |V|, m = |E|。由 V 中全部 n 个顶点和 E 中 n-1 条边构成的无向连通子图被称为 G 的一棵生成树。边权和最小的生成树被称为无向图 G 的最小生成树(Minimum Spanning Tree,MST)。
1.任意一棵最小生成树一定包含无向图中权值最小的边。 证:反证法。假设无向图存在一棵不包含权值最小边的最小生成树。若把这条权值最小边插入到树中,会形成一个环,且环上的其他每条边的权值都比这条插入的边大,因此,用这条插入的边替换环中任意一条边都能得到更小的生成树,与假设矛盾。 2.推论:给定一张带权无向图 G=(V,E),n = |V|, m = |E|。从 E 中选出 k< n-1G 的一个生成森林。若再从剩下的 m-k 条边中选 n-1-k 条添加到生成森林中,使其成为 G 的生成树,并且保证后选的边权值之和最小,则该生成树一定包含这 m-k 条边中连接生成森林的两个不连通节点的权值最小的边。
暂时不太知道怎么应用这两个结论证明下文的两个算法,这里仅做介绍
证必。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+10;
struct node{
int u, v, w;
const bool operator < (const node &t)const {return w < t.w;}
}edge[maxn];
int fa[maxn];
int Find(int x) {
if(fa[x] == x) return x;
else return fa[x] = Find(fa[x]);
}
int main(){
int n, m;
cin >> n >> m;
for(int i = 0; i< m; i++){
cin >> edge[i].u >> edge[i].v >> edge[i].w;
}
sort(edge,edge+m);
for(int i = 1; i <=n; i++){
fa[i] = i;
}
int cnt = 0, ans = 0;//cnt存加入森林的边数,判断是否存在生成树,若边树等于n-1,则存在生成树。
for(int i = 0; i< m; i++){
int x = Find(edge[i].u), y = Find(edge[i].v);
if(x == y) continue;
fa[x] = y;
ans += edge[i].w;
cnt++;
}
if(cnt != n-1) puts("impossible");
else cout << ans << endl;
}
prim无优化版本
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int mp[maxn][maxn], d[maxn], n, m;
bool sta[maxn];
int prim() {
memset(d, 0x3f, sizeof(d));//d数组初始化为无穷大
int res = 0;//记录最小生成树的总权值和
for (int i = 0; i < n; i++) {//这里i从0开始,第一次循环找到的最小点为1号点(相当于S集合的初始化)
int x = 0;
for (int j = 1; j <= n; j++)//寻找S集合中距离T最近的点
if (!sta[j] && (x == 0 || d[x] > d[j])) x = j;
if (i && d[x] == 0x3f3f3f3f) return 0x3f3f3f3f;//如果找到的最小点与S集合无边(这里显示为无穷大),则没有最小生成树,直接返回无穷大。
sta[x] = 1;//点入S集合
for (int j = 1; j <= n; j++) {//遍历入S集合的点的所有边,更新另一端点到S的距离
if (!sta[j]) d[j] = min(d[j], mp[j][x]);
}
if (i) res += d[x];//第一次循环找的是1号点,不用加边权
}
return res;
}
int main() {
cin >> n >> m;
memset(mp, 0x3f, sizeof mp);
for (int i = 1; i <= n; i++) mp[i][i] = 0;//记得初始化
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
mp[u][v] = mp[v][u] = min(mp[u][v], w);//如果有重边则这样处理
}
int ans = prim();
if (ans == 0x3f3f3f3f)//返回无穷大说明无最小生成树,输出impossible
puts("impossible");
else
cout << ans << endl;
}
优先队列优化
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int mp[maxn][maxn], d[maxn], n, m;
bool sta[maxn];
int cnt;
int prim() {
memset(d, 0x3f, sizeof(d));
int res = 0;
d[1] = 0;
priority_queue<pair<int, int>> q;
q.push({0, 1});
while (cnt < n && q.size()) {//循环条件与朴素做法有些不同,第一个条件代表最小生成树的条件,第二个条件判断队列是否为空
auto now = q.top();
q.pop();
int x = now.second;
if (sta[x]) continue;//如果属于S集合则不操作
res += -now.first;
sta[x] = 1;
cnt++;
for (int j = 1; j <= n; j++) {
if (!sta[j] && d[j] > mp[j][x]) {
d[j] = mp[j][x];
q.push({-d[j], j});//把更新过的值压入队列待匹配,这里取负号是因为我们要取最小值,而优先队列默认输出最大值
}
}
}
return res;
}
int main() {
cin >> n >> m;
memset(mp, 0x3f, sizeof mp);
for (int i = 1; i <= n; i++) mp[i][i] = 0;
for (int i = 0; i < m; i++) {
int u, v, w;
cin >> u >> v >> w;
mp[u][v] = mp[v][u] = min(mp[u][v], w);
}
int ans = prim();
if (cnt != n)//集合中没有n个点说明不存在最小生成树
puts("impossible");
else
cout << ans << endl;
}