前言
之前只会Prim的code,现在又学会了kruskal的算法,具体的算法原理这里就不详细讲了,想看原理的可以看其他大佬的博客,本人实在太懒。我在这里就放上code思路。
题目:公路村村通
题目描述
现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
输入
输入数据包括城镇数目正整数N(≤1000)和候选道路数目M(≤3N);随后的M行对应M条道路,每行给出3个正整数,分别是该条道路直接连通的两个城镇的编号以及该道路改建的预算成本。为简单起见,城镇从1到N编号。
输出
输出村村通需要的最低成本。如果输入数据不足以保证畅通,则输出−1,表示需要建设更多公路。
样例输入 复制
6 15
1 2 5
1 3 3
1 4 7
1 5 4
1 6 2
2 3 4
2 4 6
2 5 2
2 6 6
3 4 6
3 5 1
3 6 1
4 5 10
4 6 8
5 6 3
样例输出 复制
12
思路
用Prim和Kruskal均可,时间复杂度分别为O(mlogm)和O(n^2+m)(一般的Prim算法)。
先将所有边按权值排序,依次从小到大选权值较小的边,若没有与之前建成的树形成环,那么该边加入集合中,对于有n个节点的图,最小生成树的边数为n-1,当集合中有n-1个边时,最小生成树创建完成。
这里如何判断是否有环,当时我也是被这个问题难住,其实并查集可以解决这个问题,如果一个边的两个顶点是在一个集合中,所生成的最小生成树的每个节点的祖宗都是同一点。因此,如果两个点的祖宗不同,那么该边加入已创建的集合中就不会生成环。
code
里面Union函数是并查集中的合并操作
/*Kruskal求最小生成树*/
#include <iostream>
#include <algorithm>
using namespace std;
#define N 200005
#define M 200005
struct edge {
int u;//起点
int v;//终点
int w;//权值
};
edge a[M];
//int dis[N][N];
int fa[N];//父节点
int m, n;//边数和顶点数
void Init() { //初始化
for (int i = 1; i <= n; i++)
fa[i] = i;
}
int Find(int x) { //查找祖宗
if (x != fa[x])
return fa[x] = Find(fa[x]);
return fa[x];
}
void Union(int x, int y) { //合并x,y节点
int fx = fa[x], fy = fa[y];
if (fx != fy)
fa[x] = fy; //只改祖宗即可
}
bool cmp(edge a, edge b) {//自定义排序
return a.w < b.w;
}
int Kruskal() {
sort(a, a + m, cmp);
Init();
int num = 0;
int ans = 0;
for (int i = 0; i < m; i++) {
int fx = Find(a[i].u), fy = Find(a[i].v);
if (fx != fy) { //若起点和终点祖宗不同(不在一个集合)《该边可以放入集合,并经行合并操作
fa[fx] = fy; //合并(记住,只需要在祖宗处改变)
num++;//集合的顶点加一
// cout << a[i].w << endl;
ans += a[i].w; //加上边权值
}
if (num == n - 1)
break;//若集合中顶点数为n-1时,最小生成树已经生成完毕
}
if (num != n - 1)
return -1;
return ans;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> a[i].u >> a[i].v >> a[i].w;
}
int ans = Kruskal();
cout << ans << endl;
}