Kruskal算法,采用了边贪心的策略,理解难度比Prim算法要低得多。
Kruskal算法基本思想为:在初始状态时隐去图中所有边,这样图中每个顶点都自成一个连通块。之后执行以下步骤:
1、对所有边按边权大小从小到大进行排序。
2、按边权从小到大一条一条加入当前最小生成树中。如果加入的边在最小生成树中所连接的两个顶点不在同一个连通块中(也就是加入这条边后,在树中不会形成环),就将边加入,否则就舍弃不加入这条边。
3、反复执行步骤2,直到最小生成树中的边数等于总顶点数减1或是测试完所有边时结束。结束时,如果最小生成树的边数小于总顶点数减1,说明该图不连通。(最小生成树性质:其边数等于顶点数减1,且树内一定不会有环)
可以参考B站上的算法动画演示,更加直观。接下来直接上代码:
1、首先是边的定义,定义结构体来存放一条边两个端点编号和边权:
struct edge{
int u, v; //边的两个端点编号
int cost; //边权
}E[maxn]; //最多有maxn条边
2、接下来,自定义一个排序函数,用来实现边权从小到大排序:
bool cmp(edge a, edge b){ //自定义升序函数
return < ;
}
3、判断加入的边在最小生成树中所连接的两个顶点在不在同一个连通块中,以及将边加入最小生成树的具体实现。实现这两个功能,用到了并查集算法(不清楚的建议先去学习下并查集算法,Kruskal的实现会用到并查集)。下面是并查集查询函数:
int findFather(int x){ //查询根结点+路径压缩
if(father[x] != x) father[x] = findFather(father[x]);
return father[x];
}
4、Kruskal代码主体:
其中,N顶点个数,M为图的边数,ans最小生成树边权之和,Num_Edge最小生成树当前边数。
void kruskal(){
for(int i = 1; i <= N; i++){ //并查集初始化
father[i] = i;
}
sort(E, E + M, cmp); //按边权从小到大排序
for(int i = 0; i < M; i++){ //枚举所有边
if(findFather(E[i].u) != findFather(E[i].v)){ //若不在一个集合中
father[findFather(E[i].u)] = findFather(E[i].v); //合并集合
ans += E[i].cost; //边权之和增加
++Num_Edge; //当前生成树边数加1
if(Num_Edge == N - 1) break; //边数等于顶点数减一跳出
/*↑因为最小生成树的边数等于顶点数减一↑*/
}
}
if(Num_Edge == N - 1) cout << ans; //返回最小生成树边权之和
else cout << "-1" ;//无法连通时返回-1
}
5、完整实现(包括输入输出,返回最小生成树边权之和):
#include<bits/stdc++.h>
using namespace std;
const int maxn = 222222;
int N, M; //N顶点个数,M为图的边数
int X, Y, Z; //X,Y分别为图的两端点编号,Z是边权
int ans = 0; //最小生成树边权之和
int Num_Edge = 0; //最小生成树当前边数
int father[maxn]; //并查集
struct edge{
int u, v; //边的两个端点编号
int cost; //边权
}E[maxn]; //最多有maxn条边
bool cmp(edge a, edge b){ //自定义升序函数
return < ;
}
int findFather(int x){ //查询根结点+路径压缩
if(father[x] != x) father[x] = findFather(father[x]);
return father[x];
}
void kruskal(){
for(int i = 1; i <= N; i++){ //并查集初始化
father[i] = i;
}
sort(E, E + M, cmp); //按边权从小到大排序
for(int i = 0; i < M; i++){ //枚举所有边
if(findFather(E[i].u) != findFather(E[i].v)){ //若不在一个集合中
father[findFather(E[i].u)] = findFather(E[i].v); //合并集合
ans += E[i].cost; //边权之和增加
++Num_Edge; //当前生成树边数加1
if(Num_Edge == N - 1) break; //边数等于顶点数减一跳出
/*↑因为最小生成树的边数等于顶点数减一↑*/
}
}
if(Num_Edge == N - 1) cout << ans; //返回最小生成树边权之和
else cout << "-1" ;//无法连通时返回-1
}
int main(){
cin >> N >> M;
for(int i = 0; i < M; i++){
cin >> E[i].u >> E[i].v >> E[i].cost;
}
kruskal();
return 0;
}