最近在复习数据结构,所以想起了之前做的一个最小生成树算法。用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思想是在带权连通图中,不断地在排列好的边集合中找到最小的边,如果该边满足得到最小生成树的条件,就将其构造,直到最后得到一颗最小生成树。

图的顶点存储结构体:

 //结构体定义,储存图的顶点
typedef struct {
int from; //边的起始顶点
int to; //边的终止顶点
int cost; //边的权值
}Edge;

问题:顶点编号的类型。

  好的程序应该可以扩展,不论顶点用0,1,2...  顺序编号还是用5,2,1,7... 乱序编号还是用a,b,c...  英文编号都应该可以做到兼容通过,所以在存储图的节点的时候我做了一个映射,就是不论输入的什么编号一律转换成顺序编号0,1,2...,在最后输出的时候再把编号映射回原来的编号,这样就可以应对不同而顶点编号。如下面程序:

 for(i = ;i < edgeNum; i++){
printf("请输入第 %d 条边!\n",i+);
scanf(" %c %c %d",&from,&to,&cost);
edge_temp[i][] = judge_num(from);
edge_temp[i][] = judge_num(to);
edge_temp[i][] = cost;
}
//对输入的边和点信息进行堆排序
HeapSort(edge_temp,edgeNum);
int j;
for(j = ;j < edgeNum; j++){
edge[j].from = edge_temp[j][];
edge[j].to = edge_temp[j][];
edge[j].cost = edge_temp[j][];
}

每次输入顶点后都会先保存到临时存储数组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][]; //用于存储堆排序边点信息 Edge edge[MAXE]; //用于存储边点信息
Edge min_edge[MAXE]; //用于存储最小生成树边点信息 char judge_num_int[MAXE];
int inputs = ;
void HeapSort(int array[MAXE][],int length);
int judge_num(char from); //save_point()函数,存储图的点边信息;
void save_point(){
char from;
char to;
int cost = ;
int i;
for(i = ;i < edgeNum; i++){
printf("请输入第 %d 条边!\n",i+);
scanf(" %c %c %d",&from,&to,&cost); edge_temp[i][] = judge_num(from);
edge_temp[i][] = judge_num(to);
edge_temp[i][] = cost;
}
//对输入的边和点信息进行堆排序
HeapSort(edge_temp,edgeNum);
int j;
for(j = ;j < edgeNum; j++){
edge[j].from = edge_temp[j][];
edge[j].to = edge_temp[j][];
edge[j].cost = edge_temp[j][];
}
} int judge_num(char str){
int n1 = ;
for(int j1 = ;j1 < edgeNum * ; j1++){
if(str == judge_num_int[j1]){
n1++;
}
}
if(n1 == ){
judge_num_int[inputs] = str;
inputs++;
}
int return_num = ;
for(int j2 = ;j2 < edgeNum * ; 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();
int i;
for(i = ;i <= nodeNum; i++) {
if(oneedge != judge(i)){
return false;
}
}
return true;
} //kruskal算法
void kruskal(){
min_cost = ;//最小生成树
//初始化辅助回路判断数组
int m;
for(m = ;m < MAXE;m++) {
judge_temp[m] = m;
} int edge_num = ;//记录最小生成树的边数
int i;
for(i = ;i < edgeNum; i++){
//小于总节点数
if(edge_num != nodeNum - ){
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][],int i,int nLength){
int nChild;
for(; *i + < nLength; i = nChild){ //子结点的位置=2*(父结点位置)+1
nChild = *i + ;
//得到子结点中较大的结点
if(nChild < nLength- && array[nChild+][] > array[nChild][]){
++nChild;
}
//如果较大的子结点大于父结点那么把较大的子结点往上移动,替换它的父结点
if(array[i][] < array[nChild][]){
int temp_arr2[];
temp_arr2[] = array[i][];
temp_arr2[] = array[i][];
temp_arr2[] = array[i][]; array[i][] = array[nChild][];
array[i][] = array[nChild][];
array[i][] = array[nChild][]; array[nChild][] = temp_arr2[];
array[nChild][] = temp_arr2[];
array[nChild][] = temp_arr2[];
}else{
break;//否则退出循环
}
}
} //堆排序算法
void HeapSort(int array[MAXE][],int length){
//调整序列的前半部分元素,调整完之后第一个元素是序列的最大的元素
//length/2-1是最后一个非叶节点,此处"/"为整除
int j;
for( j= length/ - ; j >= ; --j){
HeapAdjust(array,j,length);
}
//从最后一个元素开始对序列进行调整,不断的缩小调整的范围直到第一个元素
int i;
for(i = length - ; i > ; --i){
//把第一个元素和当前的最后一个元素交换,
//保证当前的最后一个位置的元素都是在现在的这个序列之中最大的
int temp_arr1[]; //构建二维数组的原因:交换后保证数组中其他属性值同时交换
temp_arr1[] = array[i][];
temp_arr1[] = array[i][];
temp_arr1[] = array[i][]; array[i][] = array[][];
array[i][] = array[][];
array[i][] = array[][]; array[][] = temp_arr1[];
array[][] = temp_arr1[];
array[][] = temp_arr1[]; //不断缩小调整heap的范围,每一次调整完毕保证第一个元素是当前序列的最大值
HeapAdjust(array,,i);
}
} //输出最小生成树的信息(包括边点和权值)
void output(){
if(is_judge()){
printf("最小生成树:\n");
printf("起点 -> 终点 路径长:\n");
for(int i = ;i < nodeNum-; 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 < || edgeNum < ){
printf("输入的数据不合法\n");
printf("请输入 节点数 边数(中间需用空格隔开):\n");
return ;
}else if(nodeNum > || edgeNum > ){
printf("输入的边或者点不能大于50\n");
printf("请输入 节点数 边数(中间需用空格隔开):\n");
return ;
}else{
printf("请输入 开始节点 终止节点 该边的权值(中间需用空格隔开,回车换行):\n");
printf("共 %d 条边\n",edgeNum);
for(int m = ;m < MAXE; m++) {
judge_num_int[m] = '-';
}
inputs = ;
save_point(); //存储边点信息
kruskal(); //算法执行
output(); //输出执行结果
}
}
return ;
}

运行结果如下:

最小生成树的Kruskal算法实现的更多相关文章

  1. 数据结构与算法--最小生成树之Kruskal算法

    数据结构与算法--最小生成树之Kruskal算法 上一节介绍了Prim算法,接着来看Kruskal算法. 我们知道Prim算法是从某个顶点开始,从现有树周围的所有邻边中选出权值最小的那条加入到MST中 ...

  2. 邻接矩阵c源码(构造邻接矩阵,深度优先遍历,广度优先遍历,最小生成树prim,kruskal算法)

    matrix.c #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include < ...

  3. HDU1875——畅通工程再续(最小生成树:Kruskal算法)

    畅通工程再续 Description相信大家都听说一个“百岛湖”的地方吧,百岛湖的居民生活在不同的小岛中,当他们想去其他的小岛时都要通过划小船来实现.现在政府决定大力发展百岛湖,发展首先要解决的问题当 ...

  4. 23最小生成树之Kruskal算法

    图的最优化问题:最小生成树.最短路径 典型的图应用问题 无向连通加权图的最小生成树 有向/无向加权图的最短路径 四个经典算法 Kruskal算法.Prim算法---------------最小生成树 ...

  5. 最小生成树的Kruskal算法

        库鲁斯卡尔(Kruskal)算法是一种按照连通网中边的权值递增的顺序构造最小生成树的方法.Kruskal算法的基本思想是:假设连通网G=(V,E),令最小生成树的初始状态为只有n个顶点而无边的 ...

  6. 算法学习记录-图——最小生成树之Kruskal算法

    之前的Prim算法是基于顶点查找的算法,而Kruskal则是从边入手. 通俗的讲:就是希望通过 边的权值大小 来寻找最小生成树.(所有的边称为边集合,最小生成树形成的过程中的顶点集合称为W) 选取边集 ...

  7. 图论之最小生成树之Kruskal算法

    Kruskal算法,又称作为加边法,是配合并查集实现的. 图示: 如图,这是一个带权值无向图我们要求它的最小生成树. 首先,我们发现在1的所有边上,连到3的边的边权值最小,所以加上这条边. 然后在3上 ...

  8. 【最小生成树之Kruskal算法】

    看完之后推荐再看一看[最小生成树之Prim算法]-C++ 定义:一个有 n 个结点的连通图的生成树是原图的极小连通子图,且包含原图中的所有 n 个结点,并且有保持图连通的最少的边.最小生成树可以用kr ...

  9. 【转载】最小生成树之Kruskal算法

    给定一个无向图,如果它任意两个顶点都联通并且是一棵树,那么我们就称之为生成树(Spanning Tree).如果是带权值的无向图,那么权值之和最小的生成树,我们就称之为最小生成树(MST, Minim ...

随机推荐

  1. 通过OnResultExecuted设置返回内容为JSONP

    public class JsonpAttribute : ActionFilterAttribute { /// <summary> /// 在执行操作结果后更改返回结果 /// < ...

  2. oracle 修改密码

    [oracle@t-ecif03-v-szzb ~]$ sqlplus / as sysdba SQL*Plus: Release 11.2.0.3.0 Production on Mon Nov 1 ...

  3. 运维请注意:”非常危险“的Linux命令大全

    Linux命令是一种很有趣且有用的东西,但在你不知道会带来什么后果的时候,它又会显得非常危险.所以,在输入某些命令前,请多多检查再敲回车. rm –rf rm –rf是删除文件夹和里面附带内容的一种最 ...

  4. 转 PresentViewController切换界面

    视图切换,没有NavigationController的情况下,一般会使用presentViewController来切换视图并携带切换时的动画, 其中切换方法如下: – presentViewCon ...

  5. ngInclude与script加载模板

    ng-include: 官网实例: <p>ng-include:</p> <select ng-model="template" ng-options ...

  6. G-FAQ – Why is Bit Depth Important?

    直接抄: https://apollomapping.com/2012/August/article15.html For this month’s Geospatial Frequently Ask ...

  7. codevs1204 寻找子串位置

    题目描述 Description 给出字符串a和字符串b,保证b是a的一个子串,请你输出b在a中第一次出现的位置. 输入描述 Input Description 仅一行包含两个字符串a和b 输出描述  ...

  8. PropertiesFactoryBean PropertyPlaceholderConfigurer 区别

    正如 stackoverflow上说的,PropertiesFactoryBean 是PropertiesLoaderSupport 直接的实现类, 专门用来管理properties文件的工厂bean ...

  9. thinkphp3.2.3关于模板使用之一二

    1.包含文件 使用场景:比如我们在编写网页布局的时候,可能每一个网页的头和脚是相同的,此时如果给每一个网页分别设置,未免太麻烦了.此时就可以使用带包含文件. 首先检查配置文件查看我们的主题目录在哪儿, ...

  10. mac 上的 python

    1.mac 上的 python 自己感觉很乱 1.额外安装的 自带的 python27-apple /System/Library/Frameworks/Python.framework/Versio ...