Kruskal算法实现最小生成树(C++实现)

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 a.cost < b.cost;
} 

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 a.cost < b.cost;
} 

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;
}

如果是稠密图,用Prim算法;如果是稀疏图,用Kruskal算法。

  • 5
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值