导入
自从上次与最小生成树们玩了一上午的捉迷藏,他们就决定不让你使用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;
}