04贪心算法-kruskal算法
问题描述:
给定一个无向图,设计算法找出其中的最小生成树。
思路分析:
kruskal算法属于贪心算法,因此满足贪心算法的本质:局部最优解一定是全局最优解。在最小生成树中的应用即为,一个无向图中包含最小权值边点两个点,一定在最小生成树中,因此,只需要对该无项图中的顶点进行排序,按顺序找出边权值最小的顶点即可。但这个问题实际包含了两个问题:
1、如何对边进行排序?
2、如果新加入的边使得该最小生成树形成回路,如何解决?
针对 问题1:对无向图的各个边进行从小到大的堆排序(堆排序在此处的应用时间复杂度最低)
针对 问题2:将无向图的边加入并查集,通过并查集来判断是否构成环路,如果构成环路,即跳过该边。
(注:由于本文着重讲解kruskal算法,堆排序部分便不再赘述,下方算法也直接调用该函数)
算法分析:
假设无向图的边已经按照从小到大顺序排好,则此时kruskal算法的内核就是并查集的使用。kruskal算法步骤如下:
1、初始化一个parent数组,用于构造一棵树,数组下标为根序号,数组值为顶点序号,如parent[3]=4表示顶点4的根为3。
2、按照边权值由小到大的顺序,依次扫描边,并找出该边中顶点的根,如果新加入的边的顶点的根与已经加入边的顶点的根不相等,则将两个边进行合并,即将新的边加入最小生成树;如果新加入的边的顶点的根与已经加入顶点的根相等,则构成环路,舍弃该边,继续执行。
3、循环上述步骤。
笔记:(-from MIT算法导论课程)
kruskal算法代码:
#include<stdio.h>
#include<stdlib.h>
#define vertices 6
//对并查集进行初始化,所有值初始化为-1
void initialise(int parent[],rank[]){
int i;
for(i=0;i<vertices;i++){
parent[i] = -1;
rank[i] = 0;
}
}
//找根结点,用于连接和判断
int find_root(int x,int parent[]){
int x_root = x;
if(x_root!=-1){
x_root = parent[x_root];
}
return x_root;
}
//并查集主体,找到根后,判断根是否相同,如果相同,则有环,如果不同,则进行连接
void union_vertices(int x,int y,int parent[],rank[]){
//当根不相等时:谁的根高,谁就做父结点。
if(rank[x_root] > rank[y_root]){
parent[y_root] = x_root;
}else if(rank[y_root] > rank[x_root]){
parent[x_root] = y_root;
}else{
parent[x_root] = y_root;
rank[y_root]++;
}
}
//结构体定义边的两个顶点和权重
typedef struct edge{
int a,b;
int weight;
}
//kruskal算法
void MST_kruskal(graph g,edge *edges,int parent[],int rank[]){
//对边权重进行堆排序
heap_sort(edges);
//对边进行循环,找边的两个顶点的根,如果根不相等,则进行连接
for(int i=0;i<vertices;i++){
int a_root=find_root(edge[i].a,parent);
int b_root= find_root(edge[i].b,parent);
if(a_root!=b_root)
union_vertices(a_root,b_root,parent,rank);
}
}
int main(){
// 无向图的初始化
int parent[vertices] = {0};
int rank[vertices];
for(int i=0;i<6;i++){
scanf(“%d%d%d”,&edge[i].a,&edge[i].b,edge[i].weight);
}
initialise(parent,rank);
MST_kruskal(g,edges,parent,rank);
return 0;
}