您的位置:首页 > 其它

夕拾算法进阶篇:35)最小生成树Kruskal(图论)

2017-03-03 11:29 239 查看
Kruskal(克鲁斯卡尔)算法,同样是解决最小生成树问题的一个算法。和Prim不同,Kruskal采取的是边贪心的策略,其简洁的思想如下:

(1)对所有的边按边权从小到达排序

(2)按边权从小到大测试所有的边,如果当前测试边所连接的两个顶点不在同一个连通块中(不构成环),则把这条边加入当前最小生成树中;否则将边舍弃

(3)执行步骤(2),直到最小生成树的边数等于总顶点数减1

简单来说,Kruskal就是选择最小的边,如果不构成环,就把这条边加入最小生产树中。下面来解决代码实现的问题,首先是边的定义,由于要判定边的两端点是否在不同的连通块中,因此边的两个端点是需要的;而算法中涉及到边权,所有边权也是必须要得。于是可以定义如下的结构体:

struct Edge{
int u,v; //边的两个端点
int cost; //边权
}e[MAX]; //最多有MAX条边在解决边的定义后,还需要一个排序函数让数组e按边权从小到大排序:
bool cmp(Edge a,Edge b){
return a.cost<b.cost;
}接下来给出Kruskal的伪代码
int kruskal(){
令最小生成树的边权之和为ans,最小生产数当前的边数为e_num;
将所有的边按照边权从小到大排序
for(从小到大枚举所有边权){
if(当前测试的边的两个端点在不同的连通块中){
将该测试边加入最小生成树
ans+=测试的边权
最小生成树的当前边数e_num+1;
当前e_num等于顶点数减1时结束循环
}
}
}上面伪代码中有两个细节似乎不大直观,即
(1)如何判断测试边的两个端点是否在不同的连通块中

(2)如何将测试边加入到最小生成树中

事实上,换个角度来想。如果把每个连通块当作一个集合,那么就可以把问题转换为判断两个端点是否在同一个集合中,而这个问题就是前面讨论的并查集。并查集可以通过查询两个结点所在集合的根结点来判断它们是否在同一个集合,而合并功能恰好把上面提及到的第二个细节解决,即只要把测试边的两个端点所在的的集合合并,就能达到将边加入到最小生成树的效果。于是,根据上面的分析,可以给出如下实现代码(顶点的编号范围[0,n-1])

//获得元素x所在集合的根结点
int findFather(int x){
while(x!=father[x]){//如果自己不是根结点
x=father[x];//获得自己的父结点
}
return x;
}

int kruskal(int n,int m){ //n=顶点数 m=边数
//令最小生成树的边权之和为ans,最小生产数当前的边数为e_num;
int ans=0,e_num=0;
for(int i=0;i<n;i++){
father[i]=i; //并查集初始化
}
//将所有的边按照边权从小到大排序
sort(e,e+e_num,cmp);
for(int i=0;i<m;i++){//从小到大枚举所有边权
int fa=findFather(e[i].u);
int fb=findFather(e[i].v);
if(fa!=fb){//当前测试的边的两个端点在不同的连通块中
//合并这两个集合将该测试边加入最小生成树
father[fa]=fb;
ans+=e[i].cost;
e_num++;
if(e_num==n-1)break; //当前e_num等于顶点数减1时结束循环
}
}
if(e_num!=n-1)return -1; //图不连通
return ans;
}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: