最小生成树的分类
🐖:最小生成树一般用于无向图,有向图几乎不会用到。
方法选择:
稠密图:用朴素版Prim算法
稀疏图:用Kruskal算法
堆优化版Prim一般不常用!
朴素版Prim
大体思路可以参考Dijkstra算法,并注意相同点和不同点
思路
1.将所有距离都初始化为+ ∞
dist[1] = 0,dist[i] = + ∞ (dist数组表示起点到i点的距离)
2.n次迭代
for(i = 0; i < n; i ++ )
①找到不在集合当中的距离最近的点(s表示当前已经在连通块中的点),赋给t
②用t来更新其他的点到集合的距离(注意和dijkstra算法的区别)
③把t加到集合中去 st[t] = true
注:到集合的距离:连向集合内部的点中长度最小的边
典例——AcWing 858. Prim算法求最小生成树
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510,INF = 0x3f3f3f3f;
int n,m;
int g[N][N],dist[N];
bool st[N];
int prim()
{
memset(dist,0x3f,sizeof dist);
int res = 0; //连通块中边的权重之和
for(int i = 0; i < n; i ++ )
{
int t = -1;
for(int j = 1; j <= n; j ++ )
if(!st[j] && (t == -1 || dist[t] > dist[j])) //找到不在集合当中的距离最近的点
t = j;
if(i && dist[t] == INF) return INF; //不是第一个点且不连通,不存在最小生成树
if(i) res += dist[t];
for(int j = 1; j <= n; j ++ ) //用t更新点到集合的距离
dist[j] = min(dist[j],g[t][j]);
st[t] = true; //将t加到集合中去
}
return res;
}
int main()
{
cin >> n >> m;
memset(g,0x3f,sizeof g);
while(m -- )
{
int a,b,c;
cin >> a >> b >> c;
g[a][b] = g[b][a] = min(c,g[a][b]);
}
int t = prim();
if(t == INF) puts("impossible");
else
cout << t << endl;
return 0;
}
注:
if(i) res += dist[t];
for(int j = 1; j <= n; j ++ ) //用t更新点到集合的距离
dist[j] = min(dist[j],g[t][j]);
上面这两步顺序不能颠倒!
一定先把结果加进去,再用t点更新其他边。原因是可能有自环存在,如果先用t更新,可能会把t点的自环更新到dist中,而生成树中不能有自环存在。这样再加到结果中就是不对的。
*堆优化版Prim算法,思路和堆优化版Dijkstra算法基本一致,而且很麻烦,基本不会用。想了解堆优化的思路可以参考我之前的这篇博:最短路问题
Kruskal算法
思路:
1)先将所有边按权重从小到大排序 O(mlogm) (算法瓶颈,这一步是最慢的一步,但是这只是理论的时间复杂度,实际上Kruskal算法中,这一步的常数非常小,表现的效果也通常很好)
2)从小到大枚举每条边 a,b 权重 c
if(a,b不连通)
将这条边加入集合中
注: 第2步要用到并查集,可以看我以前的博客并查集,参考题目“连通块中点的数目“
优点:
1)不需要用邻接表等复杂的数据结构存边,开一个结构体就行了;
2)思路简单
典例 AcWing 859. Kruskal算法求最小生成树
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 100010;
int n,m;
int p[N];
//用结构体来存储所有边
struct Edge
{
int a,b,w;
//重载小于号,方便排序(按权重来排序)
bool operator< (const Edge &W)const
{
return w < W.w;
}
}edges[N];
int find(int x)
{
if(p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
cin >> n >> m;
for(int i = 0; i < m; i ++ )
{
int a,b,w;
cin >> a >> b >> w;
edges[i] = {a,b,w};
}
sort(edges,edges + m);
for(int i = 1; i <= n; i ++ ) p[i] = i; //初始化并查集
int res = 0, cnt = 0; //res:存最小生成树中所有树边的权重之和;
//cnt:存当前边的数目(即当前加入多少条边)
//从小到大枚举所有边
for(int i = 0; i < m; i ++ )
{
int a = edges[i].a, b = edges[i].b, w = edges[i].w;
a = find(a), b = find(b);
if(a != b) //判断a,b是否连通
{
p[a] = b; //将两个集合合并(即将这条边加入到集合中)
res += w;
cnt ++;
}
}
if(cnt < n - 1) puts("impossible"); //不连通
else cout << res << endl; //所有树边的长度之和
return 0;
}
本篇主要讲了Prim和Kruskal两个最小生成树的算法,欢迎大家批评指正!