全网最细Kruskal算法详解(大佬勿入orz)

写在前面:

//如果家人们有疑问的话欢迎留言
克鲁斯卡尔(Kruskal)算法通常用于求出一个连通图中的最小生成树,本文会对这种算法以及该算法的基础(最小生成树、并查集)进行详细的介绍。

最小生成树

首先明确一下概念,什么是最小生 成树呢?
现在我有一张由n个节点构成的连通图(如下)

可以看到,每个节点都有许多条边与其它节点相连,我们要让这张图变成最小生成树,只需要让每一个节点至少有一条边与其它点相连就可以了,这样最后n个顶点共有n-1条边,而每个点的边权值是一定的,我们要在保证图的联通的情况下删去一些边,使得留下的边的边权和尽可能小,最后留下的这张图就会是原图的最小生成树(如下图)。

像这样,现在的图删去了无用的边,在保证图的每个点直接或间接联通的情况下使得边权和最小,这就是最小生成树。需要强调的是,最小生成树中的边数等于点数-1。

然后呢一般Kruskal算法都是跟并查集一起用的,这样会更方便的解决问题.

Kruskal函数算法思想:

把并查集的思想与Kruskal算法的思想进行结合。Kruskal算法的思想是:从权值最小的边选起,直至选出n-1条权值最小的边为止,且这n-1条边必须不构成回路。而并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。并查集主要由一个整型数组fa[ ]和两个函数find( )、merge( )构成。数组 fa[ ] 记录了每个点的前驱节点是谁,函数 find(x) 用于查找指定节点 x 属于哪个集合即找它们各自的祖先,函数 merge(x,y) 用于合并两个节点 x 和 y 。并查集的主要作用是可以求连通分支数。我们这里是用并查集来判断所选的边是否构成回路,如果两个顶点的祖先相同即两点已经连通再加上此边肯定会构成回路所以我们不取这条边,反之则取这条边,因为我们选的边都是按权值从小到大来排序的,所以最后构造的最小生成树所花的总权值是最小的。

总的代码奉上: 一些细节在代码后面注释了.

#include<bits/stdc++.h>

#define int long long 
using namespace std;

const int N = 2e5 + 10; // 点的最大数量

struct edge{
	int u, v, w; // u,v表示两点 w表示两点之间的权值
}g[N];     // 存储两点之前关系

int fa[N]; // 定义父亲 
int l; // 记录边
int n, m; // 点数和边数
int value[N]; // 保存最后存储边的权值
pair<int, int>p[N];
int cnt = 0, ans = 0; // 记录已经选了的边数和总的权值 

void add(int u, int v, int w){ // 把边存储起来
	l++;
	g[l].u = u;
	g[l].v = v;
	g[l].w = w;
}

int findroot(int x){
	return (fa[x] == x ? x : fa[x] = findroot(fa[x])); // 标准并查集
}

void merge(int x, int y){ // 合并
	x = findroot(x);
	y = findroot(y);
	fa[x] = y;  // 将 x y 合并
}

bool cmp(edge a, edge b){
	return a.w < b.w;  // 以权值最小边排序
}

void kruskal(){
	for (int i = 1; i <= m; i++){ // 从权值最小的边开始遍历
		int xr = findroot(g[i].u), yr = findroot(g[i].v);//求两个顶点的祖先 
		if (xr != yr){
			merge(xr, yr);   // 如果祖先不同,就把他们合并
			value[cnt] = g[i].w;  //  保存权值
			p[cnt++] = {g[i].u,g[i].v}; // 保存顶点 
			ans += g[i].w;   // 保存总的权值
		}
		if (cnt == n - 1) { // 如果已经构成最小生成数,结束.
			cout << endl;  // 输出总权值
			cout << "总权值为: " << ans << "\n" << "\n";
			return;
		}
	}

void solve(){
    cout << "请输入点数和边数: " << "\n";
    cin >> n >> m; 
	for (int i = 1; i <= n; i++)
		fa[i] = i;    // 初始化  初始每个点的祖先是他自己 
		cout << "请输入各条边的顶点及权值:" << "\n";
	for (int i = 1; i <= m; i++){
		int u, v, w;
		cin >> u >> v >> w; //   输入
		add(u, v, w); //  把输入的顶点和权值记录下来
	}
	sort(g + 1, g + 1 + m, cmp); //  按边的权值升序排列 
	kruskal();
	cout << "请输出最后的最小生成树的各边的顶点及权值 :" << "\n";
	cout<<"顶点--顶点--权值" << "\n";
	   for(int i=0;i<cnt;i++)
		cout << p[i].first << "--" << p[i].second <<"--"<<value[i] << "\n";
	  // system("pause");
}
signed main(){
	int _=1;
	//cin>>_;
	while (_--){
		solve();
	}
}

推荐题目

知识需要巩固,洛谷上有很多Kruskal的模版题。童鞋们可以去坐坐看。

作者:惊鸿只为她。喜欢我的博客就点个赞吧,跪求亿赞Orz。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值