夕拾算法进阶篇: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;
}
(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;
}
相关文章推荐
- 图论-带权图的最小生成树(Kruskal)算法
- 图论之最小生成树-----克鲁斯卡尔(Kruskal)算法
- 【算法】图论_最小生成树(MST)_Kruskal
- 图论中最小生成树算法-Prim(普里姆)算法、kruskal(克鲁斯卡尔避圈法)算法、破圈算法
- 最小生成树算法(Prim和Kruskal)
- 最小生成树算法(prim&kruskal)
- 图论-带权图的最小生成树(Prim)算法
- 最小生成树两个重要的算法:Prim 和 Kruskal
- 杭电 1233 最小生成树 kruskal()算法
- 图论;最小生成树;普利姆算法;贪心策略;可用最小堆实现;
- hdu 1233 还是畅通工程(最小生成树的Prim和Kruskal两种算法的c++实现)(prim算法详解)
- hdu 1233 还是畅通工程(最小生成树的Prim和Kruskal两种算法的c++实现)(prim算法详解)
- 最小生成树 kruskal(算法)
- TopCoder 算法比赛图论实战1—最小生成树问题
- 分量算法poj 1751 Highways 最小生成树之Kruskal(克鲁斯卡尔)算法
- hdu2988Dark roads (最小生成树之kruskal 算法(主要是并查集))
- 算法:图解最小生成树之克鲁斯卡尔(Kruskal)算法
- 杭电 1233 最小生成树 kruskal()算法
- POJ 1861 Network [最小生成树算法MST-kruskal 数据结构-并查集 union-find sets]
- 克鲁斯卡尔(Kruskal)算法求最小生成树