最小生成树的Kruskal算法实现
2015-06-27 14:02
387 查看
最近在复习数据结构,所以想起了之前做的一个最小生成树算法。用Kruskal算法实现的,结合堆排序可以复习回顾数据结构。现在写出来与大家分享。
最小生成树算法思想:书上说的是在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树。说白了其实就是在含有 n 个顶点的连通网中选择 n-1 条边,构成一棵极小连通子图,并使该连通子图中 n-1 条边上权值之和达到最小,则称最小生成树。
本程序用的是克鲁斯卡尔算法(Kruskal),也可以使用prim算法实现。Kruskal思想是在带权连通图中,不断地在排列好的边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。
图的顶点存储结构体:
问题:顶点编号的类型。
好的程序应该可以扩展,不论顶点用0,1,2... 顺序编号还是用5,2,1,7... 乱序编号还是用a,b,c... 英文编号都应该可以做到兼容通过,所以在存储图的节点的时候我做了一个映射,就是不论输入的什么编号一律转换成顺序编号0,1,2...,在最后输出的时候再把编号映射回原来的编号,这样就可以应对不同而顶点编号。如下面程序:
每次输入顶点后都会先保存到临时存储数组edge_temp中,进行堆排序后再把数据白存在真正的数据中。其中判断是否形成回路借助了一个递归方法:
执行步骤:
1:在带权连通图中,将边的权值排序(本程序用的是堆排序);
2:判断是否需要选择这条边(此时的边已按权值从小到大排好序)。判断的依据是边的两个顶点是否已连通,如果连通则继续下一条;如果不连通,那么就选择使其连通。
3:循环第二步,直到图中所有的顶点都在同一个连通分量中,即得到最小生成树。
判断法则:(当将边加入到已找到边的集合中时,是否会形成回路?)
1:如果没有形成回路,那么直接将其连通。此时,对于边的集合又要做一次判断:这两个点是否在已找到点的集合中出现过?如果两个点都没有出现过,那么将这 两个点都加入已找到点的集合中;如果其中一个点在集合中出现过,那么将另一个没有出现过的点加入到集合中;如果这两个点都出现过,则不用加入到集合中。
2:如果形成回路,不符合要求,直接进行下一次操作。
重点类容就这么多,下面给出源程序,程序直接复制后可以运行,有兴趣的朋友也可以用prim算法实现。
运行结果如下:
最小生成树算法思想:书上说的是在一给定的无向图G = (V, E) 中,(u, v) 代表连接顶点 u 与顶点 v 的边(即),而 w(u, v) 代表此边的权重,若存在 T 为 E 的子集(即)且为无循环图,使得的 w(T) 最小,则此 T 为 G 的最小生成树。说白了其实就是在含有 n 个顶点的连通网中选择 n-1 条边,构成一棵极小连通子图,并使该连通子图中 n-1 条边上权值之和达到最小,则称最小生成树。
本程序用的是克鲁斯卡尔算法(Kruskal),也可以使用prim算法实现。Kruskal思想是在带权连通图中,不断地在排列好的边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。
图的顶点存储结构体:
//结构体定义,储存图的顶点 typedef struct { int from; //边的起始顶点 int to; //边的终止顶点 int cost; //边的权值 }Edge;
问题:顶点编号的类型。
好的程序应该可以扩展,不论顶点用0,1,2... 顺序编号还是用5,2,1,7... 乱序编号还是用a,b,c... 英文编号都应该可以做到兼容通过,所以在存储图的节点的时候我做了一个映射,就是不论输入的什么编号一律转换成顺序编号0,1,2...,在最后输出的时候再把编号映射回原来的编号,这样就可以应对不同而顶点编号。如下面程序:
for(i = 0;i < edgeNum; i++){ printf("请输入第 %d 条边!\n",i+1); scanf(" %c %c %d",&from,&to,&cost); edge_temp[i][0] = judge_num(from); edge_temp[i][1] = judge_num(to); edge_temp[i][2] = cost; } //对输入的边和点信息进行堆排序 HeapSort(edge_temp,edgeNum); int j; for(j = 0;j < edgeNum; j++){ edge[j].from = edge_temp[j][0]; edge[j].to = edge_temp[j][1]; edge[j].cost = edge_temp[j][2]; }
每次输入顶点后都会先保存到临时存储数组edge_temp中,进行堆排序后再把数据白存在真正的数据中。其中判断是否形成回路借助了一个递归方法:
//用于判断是否形成回路 int judge(int num){ if(num == judge_temp[num]){ return judge_temp[num]; } return judge_temp[num] = judge(judge_temp[num]); }
执行步骤:
1:在带权连通图中,将边的权值排序(本程序用的是堆排序);
2:判断是否需要选择这条边(此时的边已按权值从小到大排好序)。判断的依据是边的两个顶点是否已连通,如果连通则继续下一条;如果不连通,那么就选择使其连通。
3:循环第二步,直到图中所有的顶点都在同一个连通分量中,即得到最小生成树。
判断法则:(当将边加入到已找到边的集合中时,是否会形成回路?)
1:如果没有形成回路,那么直接将其连通。此时,对于边的集合又要做一次判断:这两个点是否在已找到点的集合中出现过?如果两个点都没有出现过,那么将这 两个点都加入已找到点的集合中;如果其中一个点在集合中出现过,那么将另一个没有出现过的点加入到集合中;如果这两个点都出现过,则不用加入到集合中。
2:如果形成回路,不符合要求,直接进行下一次操作。
重点类容就这么多,下面给出源程序,程序直接复制后可以运行,有兴趣的朋友也可以用prim算法实现。
#include <stdio.h> #include <string.h> //常量定义,边点最大数量限制50; #define MAXE 52 /* * Info:最小生成树算法源码(C语言版) * @author: zhaoyafei * time: 2015 */ //结构体定义,储存图的顶点 typedef struct { int from; //边的起始顶点 int to; //边的终止顶点 int cost; //边的权值 }Edge; int nodeNum; //顶点数; int edgeNum; //边数; int min_cost; //记录最小生成树(权值) int judge_temp[MAXE]; //记录判断是否成环 int sort[MAXE][MAXE]; //用来做排序 int edge_temp[MAXE][3]; //用于存储堆排序边点信息 Edge edge[MAXE]; //用于存储边点信息 Edge min_edge[MAXE]; //用于存储最小生成树边点信息 char judge_num_int[MAXE]; int inputs = 1; void HeapSort(int array[MAXE][3],int length); int judge_num(char from); //save_point()函数,存储图的点边信息; void save_point(){ char from; char to; int cost = 0; int i; for(i = 0;i < edgeNum; i++){ printf("请输入第 %d 条边!\n",i+1); scanf(" %c %c %d",&from,&to,&cost); edge_temp[i][0] = judge_num(from); edge_temp[i][1] = judge_num(to); edge_temp[i][2] = cost; } //对输入的边和点信息进行堆排序 HeapSort(edge_temp,edgeNum); int j; for(j = 0;j < edgeNum; j++){ edge[j].from = edge_temp[j][0]; edge[j].to = edge_temp[j][1]; edge[j].cost = edge_temp[j][2]; } } int judge_num(char str){ int n1 = 0; for(int j1 = 1;j1 < edgeNum * 2; j1++){ if(str == judge_num_int[j1]){ n1++; } } if(n1 == 0){ judge_num_int[inputs] = str; inputs++; } int return_num = 1; for(int j2 = 1;j2 < edgeNum * 2; j2++){ if(str == judge_num_int[j2]){ return_num = j2; } } return return_num; } //用于判断是否形成回路 int judge(int num){ if(num == judge_temp[num]){ return judge_temp[num]; } return judge_temp[num] = judge(judge_temp[num]); } //判断是否是一棵最小生成树 bool is_judge(){ int oneedge = judge(1); int i; for(i = 2;i <= nodeNum; i++) { if(oneedge != judge(i)){ return false; } } return true; } //kruskal算法 void kruskal(){ min_cost = 0;//最小生成树 //初始化辅助回路判断数组 int m; for(m = 0;m < MAXE;m++) { judge_temp[m] = m; } int edge_num = 0;//记录最小生成树的边数 int i; for(i = 0;i < edgeNum; i++){ //小于总节点数 if(edge_num != nodeNum - 1){ int edge_from = judge(edge[i].from); int edge_to = judge(edge[i].to); //如果形成回路则edge_from == edge_to; if(edge_from != edge_to){ //如果没有形成回路,则改变原临时数组中的值 judge_temp[edge_from] = edge_to; min_cost += edge[i].cost; //将符合的边加入到存储数组中 min_edge[edge_num].from = edge[i].from; min_edge[edge_num].to = edge[i].to; min_edge[edge_num].cost = edge[i].cost; edge_num++; } } } } //array是待调整的堆数组,i是待调整的数组元素的位置,nlength是数组的长度 //根据数组array构建大顶堆 void HeapAdjust(int array[MAXE][3],int i,int nLength){ int nChild; for(; 2*i + 1 < nLength; i = nChild){ //子结点的位置=2*(父结点位置)+1 nChild = 2*i + 1; //得到子结点中较大的结点 if(nChild < nLength-1 && array[nChild+1][2] > array[nChild][2]){ ++nChild; } //如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点 if(array[i][2] < array[nChild][2]){ int temp_arr2[3]; temp_arr2[0] = array[i][0]; temp_arr2[1] = array[i][1]; temp_arr2[2] = array[i][2]; array[i][0] = array[nChild][0]; array[i][1] = array[nChild][1]; array[i][2] = array[nChild][2]; array[nChild][0] = temp_arr2[0]; array[nChild][1] = temp_arr2[1]; array[nChild][2] = temp_arr2[2]; }else{ break;//否则退出循环 } } } //堆排序算法 void HeapSort(int array[MAXE][3],int length){ //调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素 //length/2-1是最后一个非叶节点,此处"/"为整除 int j; for( j= length/2 - 1; j >= 0; --j){ HeapAdjust(array,j,length); } //从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素 int i; for(i = length - 1; i > 0; --i){ //把第一个元素和当前的最后一个元素交换, //保证当前的最后一个位置的元素都是在现在的这个序列之中最大的 int temp_arr1[3]; //构建二维数组的原因:交换后保证数组中其他属性值同时交换 temp_arr1[0] = array[i][0]; temp_arr1[1] = array[i][1]; temp_arr1[2] = array[i][2]; array[i][0] = array[0][0]; array[i][1] = array[0][1]; array[i][2] = array[0][2]; array[0][0] = temp_arr1[0]; array[0][1] = temp_arr1[1]; array[0][2] = temp_arr1[2]; //不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值 HeapAdjust(array,0,i); } } //输出最小生成树的信息(包括边点和权值) void output(){ if(is_judge()){ printf("最小生成树:\n"); printf("起点 -> 终点 路径长:\n"); for(int i = 0;i < nodeNum-1; i++){ printf(" %c -> %c %d\n",judge_num_int[min_edge[i].from],judge_num_int[min_edge[i].to],min_edge[i].cost); } printf("min cost is : %d\n",min_cost); printf("*******************************************************************************\n"); printf("请输入 节点数 边数(中间需用空格隔开):\n"); }else{ printf("最小生成树不存在!\n"); printf("*******************************************************************************\n"); printf("请输入 节点数 边数(中间需用空格隔开):\n"); } } /* * 程序主方法; * 用于开始程序,介绍程序操作须知; */ int main(){ printf("*******************************************************************************\n"); printf("** 最小生成树(kruskal算法) ***\n"); printf("** 说明:开始程序输入图的总点数和总边数,测试程序目前边点限制最多输入50个 ***\n"); printf("** 中间用空格隔开。输入边和点信息,需要按格式:开始边 终止边 权值 ***\n"); printf("** 本次计算结束可以按要求开始下次计算。 ***\n"); printf("*******************************************************************************\n"); printf("请输入 节点数 边数(中间需用空格隔开):\n"); while(scanf("%d%d",&nodeNum,&edgeNum) != EOF){ //判断输入的边和点的合法性 if(nodeNum < 1 || edgeNum < 1){ printf("输入的数据不合法\n"); printf("请输入 节点数 边数(中间需用空格隔开):\n"); return 0; }else if(nodeNum > 50 || edgeNum > 50){ printf("输入的边或者点不能大于50\n"); printf("请输入 节点数 边数(中间需用空格隔开):\n"); return 0; }else{ printf("请输入 开始节点 终止节点 该边的权值(中间需用空格隔开,回车换行):\n"); printf("共 %d 条边\n",edgeNum); for(int m = 0;m < MAXE; m++) { judge_num_int[m] = '-'; } inputs = 1; save_point(); //存储边点信息 kruskal(); //算法执行 output(); //输出执行结果 } } return 0; }
运行结果如下:
相关文章推荐
- maven安装及在MyEclipse中 设置
- js获取来源url(上个页面的地址)
- 迪米特法则
- 【C#】【邮件】C#发送邮件出现 "指定字符串与主题所要求的形式不符"
- Android 让EditText失去焦点避免自动弹出输入法
- 在ubuntu上搭建android开发环境(4)——安装Android Studio
- 第三次月赛题解
- yum常用命令
- 74.Search a 2D Matrix&240. Search a 2D Matrix II
- Android:TextView代码动态设置colors.xml颜色
- 【swift-总结】函数
- nginx 限制IP
- poj 3103 Cutting a Block 模拟水题
- 推荐一个 python 的学习网址
- 6.27 洗刷刷 1 oj 运算符重载 复数之和 (3+4i)+(5-10i)
- The type 'Microsoft.Office.Interop.Excel.ApplicationClass' has no constructors defined
- Linux入门篇之四:VMware虚拟网络配置
- Redis命令学习-Pub/Sub(发布/订阅)
- ADF中改变table每页展示数据的行数
- HDU_2069 Coin Change(dfs)