Keruskal 算法

本文介绍了Keruskal算法,一种用于求解加权连通图最小生成树的贪心策略算法。与Prim算法的区别在于,Keruskal算法通过按边权升序排序并选择能连接两个集合的最小边。文章详细阐述了算法描述、并查集的合并与查找操作,并提供了数据处理和算法实现的步骤,最后通过例题进行实战解析。
摘要由CSDN通过智能技术生成

导入

自从上次与最小生成树们玩了一上午的捉迷藏,他们就决定不让你使用Prim算法来找他们(因为Prim算法实在是太好用了,让他们连输了好几把),现在,为了挽回你们脆弱的友谊,你只能换一种算法了,那么到底该用什么算法呢?

Keruskal 算法

于是你找来了Prim算法的好兄弟,今天的主角----- Keruskal 算法

算法描述

与Prim算法相同,Keruskal算法也是一中使用贪心的策略,在加权连通图中求最小生成树的算法

两者都使用了贪心的策略,同样的都能求出加权连通图中的最小生成树,那么,他们究竟有什么不同之处呢?

与prim算法的不同之处

想要理解Prim算法与Keruskal算法之间的关系,我们首先要明白两个算法的实现方式(即使用了什么样的方法来找出最小生成树)

Prim算法的方法是,寻找与dist集合距离最近的顶点,并将这个点标记加入到dist集合当中,从而找出最小生成树

Keruskal算法的方法则是,将每一条边按边权升序排序,然后找出能将两个集合相连的边权最小的边,从而达到找出最小生成树的目的

让我们看图说话,这是一个图,此时每个顶点的父都是自己
在这里插入图片描述

现在,我们通过贪心,找到了一些边权较小的边,然后把他们合成一个集合

在这里插入图片描述
可是接下来该选哪一条边呢,这个时候就要开始选择能把两个集合联系在一起的边了,没错,就是选择1-5 这一条边

在这里插入图片描述
以此类推,最后得到的集合就是最小生成树了

相信从两个算法实现的方法,我们也能看得出来,这两个算法有着很大的区别

算法实现

了解了这些不同后,我们就可以着手来尝试实现Keruskal算法了
不过,你真的了解并查集吗?

回顾并查集

刚才在描述Keruskal算法的实现过程时,我们提到了合并两个集合,那么,两个集合,是如何实现合并与查找的呢?

并查集—合并操作

合并操作的目的,是将两个集合合并成一个集合,那么应该如何实现这个目的呢

其实很简单,在一个集合中,每个“子”,都有一个“父”,我们只需要改变这个集合中的“父”的“父”,让他变成最后合成出的大集合中的一个子

就像图中所表述的一样(红字是这个顶点的父)
在这里插入图片描述
按照我们刚才说的来做就好了
在这里插入图片描述

这就是并查集里的合并操作

并查集—查找操作

看完合并,我们再来看看查找
查找的目的是寻找到集合中的父(如下图,里面的父是顶点一)
在这里插入图片描述
很简单,我们只需要找到一个父为自己的点就行了

数据处理

回顾完并查集后,我们终于可以回到正轨,来实现Keruskal算法了

数据的处理上,我们新添加了一个数组mf用于记录每个点的父,同时不再使用和Prim以及Dijkstra算法的排序队列,而是直接使用数组edges来存储边的信息

struct Node{
	int n1,n2,w;//前驱,ID,边权
	Node(){}
	Node(int i,int d,int l){
		n1=i;n2=d;w=l;
	}
	friend bool operator < (Node a,Node b){
		return a.w<b.w;
	}
};  
Node edges[10000];//存储边的信息
int mf[10000];//存储每一个点的“父”
内部实现

我们就按照之前的思路来实现就可以了

int find(int v){//并查集中查找的操作
	if(v==mf[v]){//如果父是自己
		return v;
	}else{
		return find(mf[v]);//找自己的父
	}
}

vector<int> Keruskal(){
	vector<int>result;
	sort(edges+1,edges+1+m);//将边按边权升序排序
	for(int i=1;i<=n;i++)mf[i]=i;//初始化,每个点的父都是自己
	int esum=0;
	for(int i=1;i<=m;i++){
		int t1=find(edges[i].n1);
		int t2=find(edges[i].n2);
		if(t1==t2)continue;//如果是同一个集合的就不用加入到最小生成树
		mf[t2]=t1;//并查集的合并操作
		result.push_back(i);
		esum++;//记录最小生成树的边的条数
		if(esum==n-1)break;//和Prim算法相同,有最小生成树时结束循环
	}
	return result;
}

例题

让我们来试试把!

题目出处:点这里

在这里插入图片描述

输入

在这里插入图片描述

输出

在这里插入图片描述

样例
输入
4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8
输出
3 6
思路

利用Keruskal算法直接求解即可

代码实现
#include<bits/stdc++.h>
using namespace std;

struct Node{
	int n1,n2,w;
	Node(){}
	Node(int i,int d,int l){
		n1=i;n2=d;w=l;
	}
	friend bool operator < (Node a,Node b){
		return a.w<b.w;
	}
};  
Node edges[10000];
int n,m;
int u,v,c;
int mf[10000];

int find(int v){
	if(v==mf[v]){
		return v;
	}else{
		return find(mf[v]);
	}
}

vector<int> Keruskal(){
	vector<int>result;
	sort(edges+1,edges+1+m);
	for(int i=1;i<=n;i++)mf[i]=i;
	int esum=0;
	for(int i=1;i<=m;i++){
		int t1=find(edges[i].n1);
		int t2=find(edges[i].n2);
		if(t1==t2)continue;
		mf[t2]=t1;
		result.push_back(i);
		esum++;
		if(esum==n-1)break;
	}
	return result;
}

int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		cin>>u>>v>>c;
		edges[i].n1=v;
		edges[i].n2=u;
		edges[i].w=c;
	}
	vector<int>Nodes=Keruskal();
	int sum=0;
	for(int ab : Nodes){
		sum=max(sum,edges[ab].w);
	}
	cout<<n-1<<" "<<sum;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值