2023年8月1日发(作者:)
h
合肥学院
计算机科学与技术系
课程设计报告
2013~2014 学年第 2 学期
课学学专指业导班教生姓程 数据结构与算法
名
号
级
师
童子轩
1204013037
12级计本3班
何立新
课程设计题目名称
用Kruskal算法求解其所有的最小生成树
2014 年 9 月
h h
题目
设计程序完成如下功能:对给定的网和起点,用Kruskal算法的基本思想求解其所有的最小生成树。
一、问题分析及问题定义
题目中的要求是用Kruskal算法来求解最小生成树,首先要弄清楚最小生成树是什么,怎么生成最小生成树以及Kruskal算法的基本思想。
最小生成树的定义是:在图论中,常常将树定义为一个无回路连通图。对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树称为图的最小生成树。在一给定的无向图G = (V, E)中,(u, v)代表连接顶点u与顶点v的边,而 w(u,v) 代表此边的权重,若存在T为 E 的子集且为无循环图,使得权重之和w(T) 最小,则此T为G的最小生成树。
最小生成树的特性有:任意一棵树的最小生成树边上的权值之和最小,最小生成树可能不唯一,因为它们的权值之和相等。大多数解决该类问题的算法都基于最小生成树的MST性质。在一个连通网N={V,{E}}中,U是顶点集V的一个非空子集。如果存在一条具有最小权值的边(u,v),其中u∈U,v∈(U-V),则必存在一棵包含(u,v)的最小生成树。
克鲁斯卡尔算法的基本思想是:假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
假如有一张图,有若干个点和边,使用克鲁斯卡尔算法生成最小生成树的基本步骤是:
(1)首先将所有的边的长度进行排序,用排序结果作为后面选择边的依据。选择长度最小的一条边,如果存在多条长度一样小的边,任选一条。
(2)如果有长度一样小的边,再选择这样的另外几条边。
(3)按照边的长度递增的顺序来选择边,如果构成环路则将这条边舍弃,选择其他边,当所有点都连通了后,最小生成树就构建完成。
所以解决问题的方案是首先选择一种方式来存储图中各点与边的信息,然后用冒泡排序对所有边的权值进行排序,再用克鲁斯卡尔算法求网的最小生成树,最后按父亲结点和子女结点集的形式输出生成树中各条边以及它们的权值。
二、数据结构的选择和概要设计
题目要求是生成最小生成树,要对图中各边的权值进行比较和选择,所以采用邻接矩阵来存储。
存储图中各点的结构体,bv表示起点,tv表示终点,w表示权值
sstruct edges{
int bv;
int tv;
h h
int w;
};
概要设计:
(1)对图中各权值进行排序
排序是通过子函数void insertsort(edgeset ge[],int e)来实现的,这里面权值的指代方式不同,在建立无向图的时候是通过边的两个顶点来存储权值,但是在权值排序的时候只需要用图中边的数组来比较各权值的大小。利用二重for循环,若一条边的权值比另一条边的权值大,然后利用数组ge[0]把两条边的起始点、结束点以及权值进行交换,然后再按照换过之后的顺序从小到大把权值输出。
(2)利用子函数int seeks(int set[],int v)来判断最长小生成树是否构成回路。按照权值排序的结果从最小权值的边开始比较。假如最小生成树的第一条边和第二条边已选定,那么判断是否构成回路就从三条边的起点和终点判断,如果两两相同的话那就构成回路了。
(3)克鲁斯卡尔算法构造最小生成树
克鲁斯卡尔的算法思想是假设连通网N=(V,{E}),则令最小生成树的起始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加到T中,否则舍去此边而选择下一条代价最小的边。以此类推,直至T中所有的顶点都在同一连通分量上为止。
三、详细设计及编码
(1)克鲁斯卡尔算法的实现
void kruskal(edgeset ge[],int n,int e){
int set[MAXE],v1,v2,i,j;
int x;
int y=1;
edgeset xy[MAXE];
for(i=1;i set[i]=0; i=1; j=1; printf("第%d个最小生成树:n",1); while(j<=e&&i<=n-1){ v1=seeks(set,ge[j].bv); v2=seeks(set,ge[j].tv); if(v1!=v2) { xy[j]=ge[j]; printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); set[v1]=v2; i++; //计数器判断顶点数是否溢出 } j++; } y++; while(ge[j-1].w==ge[j].w){ h h printf("第%d个最小生成树为:n",y);h h for(x=1;x<=j-2;x++){ printf("(%d,%d):%dn",xy[x].bv,xy[x].tv,xy[x].w); } printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); j++; y++; } } (2)各边权值的排序 void insertsort(edgeset ge[],int e){//对权值进行排序 int i,j; for(i=2;i<=e;i++) if(ge[i].w ge[0]=ge[i]; j=i-1; while(ge[0].w ge[j+1]=ge[j]; j--; } ge[j+1]=ge[0]; } } (3)判断最小生成树是否构成回路 int seeks(int set[],int v){ int i; i=v; while(set[i]>0) i=set[i]; return i; } (4)主函数 main() { edgeset ge[MAXE]; int a,n,e,i; printf("请输入顶点个数:"); scanf("%d",&n); printf("请输入边的条数:"); scanf("%d",&e); printf("请输入边的信息(起点,终点,权值):n"); for(i=1;i<=e;i++) scanf("%d,%d,%d",&ge[i].bv,&ge[i].tv,&ge[i].w); printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)h 权值)n"); :h } printf("(退出):n"); scanf("%d",&a); while(a!=2){ switch(a){ case 1:insertsort(ge,e); kruskal(ge,n,e); break; } printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); } return 1; 四、上机调试过程 (1)语法错误:程序中使用到的变量较多,既有循环控制又有图的顶点信息和边信息的变量和数组下标及顶点数和边数。所以在编程中出现了多次变量使用不当和未事先定义以及重复定义的错误,经过编译器编译之后的提示进行了修改。另外程序中有很多关于结构体的操作,常有该变量不包含于某个结构体和操作非法的错误,通过编译器的提示进行了更正。 (2)逻辑漏洞:在对图中权值进行排序的时候,最小的权值出现了0,然后会导致最小生成树的结果出现两点没有连在一起的边,后来发现代码中开始讲所有边权值初始化为0。还有就是在程序完成后发现程序只能生成一个最小生成树,当存在有两个相同的权值的情况下,不能生成所有的最小生成树。 (3)功能实现不完善:最开始程序完成的时候,忽略了要生成所有最小生成树这个要求,在特殊情况下也只能生成一个最小生成树。然后通过跟同学的讨论,分析最小生成树的最短边都相同,最长边有相同边的时候才有多种情况,所以用循环语句while(ge[j-1].w==ge[j].w)来找相同边存在的情况。 (4)经验和体会:因为我们教材上提到克鲁斯卡尔算法思想,因此,拿到这个题目,感到并不陌生,把课本的思想看过以后,大概有了一个框架,再根据题目要求明确了程序编写的流程,知道先进行什么,后进行什么。题目要求对于给自己定义的网,用克鲁斯卡尔算法求出所有的最小生成树。首先,应该根据课题的要求对题意进行分析,将所遇到的问题进行归纳和总结,在本设计中,题意要求对给定的网和起点,用克鲁斯卡尔算法的基本思想求解出所有的最小生成树,我们应该了解关于网的一些基本概念和生成树与最小生成树之间有何区别和联系,主要是要明确的理解克鲁斯卡尔算法的基本思想。其次,针对出现的问题查找相关资料将其解决,主要应该查找有关克鲁斯卡尔算法基本思想方面的详细介绍,当所有的问题得到解决后,对本设计应该有个整体的思路并通过编写各个函数模块去实现课题的功能;最后,将编写的源程序代码在计算机上调试运行,直至调试成功。 h h 五、测试结果及分析h h 图 5-1 图 5-2 六、用户使用说明 在本设计中,是通过给定任意网和起点求出最小生成树。程序最大顶点数和边数规定为20(可以在程序中任意设定大小)。 当编译、运行之后,屏幕会显示如下信息,用户可以根据以下使用说明做相关操作: 1、 请输入边数和顶点数。您可以输入在定义范围之内的任意值。h h 2 、根据提示请输入边的信息。包括起点、终点和权值。 3 、输入完成后会出来2个选项。1是用克鲁斯卡尔算法计算最小生成树,2是选择退出。 4 、 选择1后,最小生成树的结果就出来了。 七、参考文献 1.徐孝凯,数据结构实用教程,清华大学出版社,2005年7月。 2.赫阿朋,C++应用编程,电子工业出版,2003年4月。 3.谭浩强,C语言程序设计教程,高等教育出版社,2004年5月。 4.王昆仑,李红,数据结构与算法(第二版)中国铁道出版社,2012年9月 5李廉治,姜文清,郭福顺《数据结构》,大连理工大学出版社,1989年 八、附录 #include"stdio.h" #define MAXE 100 struct edges{ int bv; int tv; int w; }; typedef struct edges edgeset; int seeks(int set[],int v){ int i; i=v; while(set[i]>0) i=set[i]; return i; } void kruskal(edgeset ge[],int n,int e){ int set[MAXE],v1,v2,i,j; int x; int y=1; edgeset xy[MAXE]; for(i=1;i set[i]=0; i=1; j=1; printf("第%d个最小生成树:n",1); while(j<=e&&i<=n-1){ v1=seeks(set,ge[j].bv); v2=seeks(set,ge[j].tv); if(v1!=v2) { xy[j]=ge[j]; printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); set[v1]=v2; h h i++; //计数器判断顶点数是否溢出h h } j++; } y++; while(ge[j-1].w==ge[j].w){ printf("第%d个最小生成树为:n",y); for(x=1;x<=j-2;x++){ printf("(%d,%d):%dn",xy[x].bv,xy[x].tv,xy[x].w); } printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); j++; y++; } } void insertsort(edgeset ge[],int e){//对权值进行排序 int i,j; for(i=2;i<=e;i++) if(ge[i].w ge[0]=ge[i]; j=i-1; while(ge[0].w ge[j+1]=ge[j]; j--; } ge[j+1]=ge[0]; } } main() { edgeset ge[MAXE]; int a,n,e,i; printf("请输入顶点个数:"); scanf("%d",&n); printf("请输入边的条数:"); scanf("%d",&e); printf("请输入边的信息(起点,终点,权值):n"); for(i=1;i<=e;i++) scanf("%d,%d,%d",&ge[i].bv,&ge[i].tv,&ge[i].w); printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); while(a!=2){ switch(a){ h h case 1:insertsort(ge,e); kruskal(ge,n,e); break; } printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); } return 1; } 资料仅供参考!!! h 2023年8月1日发(作者:) h 合肥学院 计算机科学与技术系 课程设计报告 2013~2014 学年第 2 学期 课学学专指业导班教生姓程 数据结构与算法 名 号 级 师 童子轩 1204013037 12级计本3班 何立新 课程设计题目名称 用Kruskal算法求解其所有的最小生成树 2014 年 9 月 h h 题目 设计程序完成如下功能:对给定的网和起点,用Kruskal算法的基本思想求解其所有的最小生成树。 一、问题分析及问题定义 题目中的要求是用Kruskal算法来求解最小生成树,首先要弄清楚最小生成树是什么,怎么生成最小生成树以及Kruskal算法的基本思想。 最小生成树的定义是:在图论中,常常将树定义为一个无回路连通图。对于一个带权的无向连通图,其每个生成树所有边上的权值之和可能不同,我们把所有边上权值之和最小的生成树称为图的最小生成树。在一给定的无向图G = (V, E)中,(u, v)代表连接顶点u与顶点v的边,而 w(u,v) 代表此边的权重,若存在T为 E 的子集且为无循环图,使得权重之和w(T) 最小,则此T为G的最小生成树。 最小生成树的特性有:任意一棵树的最小生成树边上的权值之和最小,最小生成树可能不唯一,因为它们的权值之和相等。大多数解决该类问题的算法都基于最小生成树的MST性质。在一个连通网N={V,{E}}中,U是顶点集V的一个非空子集。如果存在一条具有最小权值的边(u,v),其中u∈U,v∈(U-V),则必存在一棵包含(u,v)的最小生成树。 克鲁斯卡尔算法的基本思想是:假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。 假如有一张图,有若干个点和边,使用克鲁斯卡尔算法生成最小生成树的基本步骤是: (1)首先将所有的边的长度进行排序,用排序结果作为后面选择边的依据。选择长度最小的一条边,如果存在多条长度一样小的边,任选一条。 (2)如果有长度一样小的边,再选择这样的另外几条边。 (3)按照边的长度递增的顺序来选择边,如果构成环路则将这条边舍弃,选择其他边,当所有点都连通了后,最小生成树就构建完成。 所以解决问题的方案是首先选择一种方式来存储图中各点与边的信息,然后用冒泡排序对所有边的权值进行排序,再用克鲁斯卡尔算法求网的最小生成树,最后按父亲结点和子女结点集的形式输出生成树中各条边以及它们的权值。 二、数据结构的选择和概要设计 题目要求是生成最小生成树,要对图中各边的权值进行比较和选择,所以采用邻接矩阵来存储。 存储图中各点的结构体,bv表示起点,tv表示终点,w表示权值 sstruct edges{ int bv; int tv; h h int w; }; 概要设计: (1)对图中各权值进行排序 排序是通过子函数void insertsort(edgeset ge[],int e)来实现的,这里面权值的指代方式不同,在建立无向图的时候是通过边的两个顶点来存储权值,但是在权值排序的时候只需要用图中边的数组来比较各权值的大小。利用二重for循环,若一条边的权值比另一条边的权值大,然后利用数组ge[0]把两条边的起始点、结束点以及权值进行交换,然后再按照换过之后的顺序从小到大把权值输出。 (2)利用子函数int seeks(int set[],int v)来判断最长小生成树是否构成回路。按照权值排序的结果从最小权值的边开始比较。假如最小生成树的第一条边和第二条边已选定,那么判断是否构成回路就从三条边的起点和终点判断,如果两两相同的话那就构成回路了。 (3)克鲁斯卡尔算法构造最小生成树 克鲁斯卡尔的算法思想是假设连通网N=(V,{E}),则令最小生成树的起始状态为只有n个顶点而无边的非连通图T=(V,{}),图中每个顶点自成一个连通分量。在E中选择代价最小的边,若该边依附的顶点落在T中不同的连通分量上,则将此边加到T中,否则舍去此边而选择下一条代价最小的边。以此类推,直至T中所有的顶点都在同一连通分量上为止。 三、详细设计及编码 (1)克鲁斯卡尔算法的实现 void kruskal(edgeset ge[],int n,int e){ int set[MAXE],v1,v2,i,j; int x; int y=1; edgeset xy[MAXE]; for(i=1;i set[i]=0; i=1; j=1; printf("第%d个最小生成树:n",1); while(j<=e&&i<=n-1){ v1=seeks(set,ge[j].bv); v2=seeks(set,ge[j].tv); if(v1!=v2) { xy[j]=ge[j]; printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); set[v1]=v2; i++; //计数器判断顶点数是否溢出 } j++; } y++; while(ge[j-1].w==ge[j].w){ h h printf("第%d个最小生成树为:n",y);h h for(x=1;x<=j-2;x++){ printf("(%d,%d):%dn",xy[x].bv,xy[x].tv,xy[x].w); } printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); j++; y++; } } (2)各边权值的排序 void insertsort(edgeset ge[],int e){//对权值进行排序 int i,j; for(i=2;i<=e;i++) if(ge[i].w ge[0]=ge[i]; j=i-1; while(ge[0].w ge[j+1]=ge[j]; j--; } ge[j+1]=ge[0]; } } (3)判断最小生成树是否构成回路 int seeks(int set[],int v){ int i; i=v; while(set[i]>0) i=set[i]; return i; } (4)主函数 main() { edgeset ge[MAXE]; int a,n,e,i; printf("请输入顶点个数:"); scanf("%d",&n); printf("请输入边的条数:"); scanf("%d",&e); printf("请输入边的信息(起点,终点,权值):n"); for(i=1;i<=e;i++) scanf("%d,%d,%d",&ge[i].bv,&ge[i].tv,&ge[i].w); printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)h 权值)n"); :h } printf("(退出):n"); scanf("%d",&a); while(a!=2){ switch(a){ case 1:insertsort(ge,e); kruskal(ge,n,e); break; } printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); } return 1; 四、上机调试过程 (1)语法错误:程序中使用到的变量较多,既有循环控制又有图的顶点信息和边信息的变量和数组下标及顶点数和边数。所以在编程中出现了多次变量使用不当和未事先定义以及重复定义的错误,经过编译器编译之后的提示进行了修改。另外程序中有很多关于结构体的操作,常有该变量不包含于某个结构体和操作非法的错误,通过编译器的提示进行了更正。 (2)逻辑漏洞:在对图中权值进行排序的时候,最小的权值出现了0,然后会导致最小生成树的结果出现两点没有连在一起的边,后来发现代码中开始讲所有边权值初始化为0。还有就是在程序完成后发现程序只能生成一个最小生成树,当存在有两个相同的权值的情况下,不能生成所有的最小生成树。 (3)功能实现不完善:最开始程序完成的时候,忽略了要生成所有最小生成树这个要求,在特殊情况下也只能生成一个最小生成树。然后通过跟同学的讨论,分析最小生成树的最短边都相同,最长边有相同边的时候才有多种情况,所以用循环语句while(ge[j-1].w==ge[j].w)来找相同边存在的情况。 (4)经验和体会:因为我们教材上提到克鲁斯卡尔算法思想,因此,拿到这个题目,感到并不陌生,把课本的思想看过以后,大概有了一个框架,再根据题目要求明确了程序编写的流程,知道先进行什么,后进行什么。题目要求对于给自己定义的网,用克鲁斯卡尔算法求出所有的最小生成树。首先,应该根据课题的要求对题意进行分析,将所遇到的问题进行归纳和总结,在本设计中,题意要求对给定的网和起点,用克鲁斯卡尔算法的基本思想求解出所有的最小生成树,我们应该了解关于网的一些基本概念和生成树与最小生成树之间有何区别和联系,主要是要明确的理解克鲁斯卡尔算法的基本思想。其次,针对出现的问题查找相关资料将其解决,主要应该查找有关克鲁斯卡尔算法基本思想方面的详细介绍,当所有的问题得到解决后,对本设计应该有个整体的思路并通过编写各个函数模块去实现课题的功能;最后,将编写的源程序代码在计算机上调试运行,直至调试成功。 h h 五、测试结果及分析h h 图 5-1 图 5-2 六、用户使用说明 在本设计中,是通过给定任意网和起点求出最小生成树。程序最大顶点数和边数规定为20(可以在程序中任意设定大小)。 当编译、运行之后,屏幕会显示如下信息,用户可以根据以下使用说明做相关操作: 1、 请输入边数和顶点数。您可以输入在定义范围之内的任意值。h h 2 、根据提示请输入边的信息。包括起点、终点和权值。 3 、输入完成后会出来2个选项。1是用克鲁斯卡尔算法计算最小生成树,2是选择退出。 4 、 选择1后,最小生成树的结果就出来了。 七、参考文献 1.徐孝凯,数据结构实用教程,清华大学出版社,2005年7月。 2.赫阿朋,C++应用编程,电子工业出版,2003年4月。 3.谭浩强,C语言程序设计教程,高等教育出版社,2004年5月。 4.王昆仑,李红,数据结构与算法(第二版)中国铁道出版社,2012年9月 5李廉治,姜文清,郭福顺《数据结构》,大连理工大学出版社,1989年 八、附录 #include"stdio.h" #define MAXE 100 struct edges{ int bv; int tv; int w; }; typedef struct edges edgeset; int seeks(int set[],int v){ int i; i=v; while(set[i]>0) i=set[i]; return i; } void kruskal(edgeset ge[],int n,int e){ int set[MAXE],v1,v2,i,j; int x; int y=1; edgeset xy[MAXE]; for(i=1;i set[i]=0; i=1; j=1; printf("第%d个最小生成树:n",1); while(j<=e&&i<=n-1){ v1=seeks(set,ge[j].bv); v2=seeks(set,ge[j].tv); if(v1!=v2) { xy[j]=ge[j]; printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); set[v1]=v2; h h i++; //计数器判断顶点数是否溢出h h } j++; } y++; while(ge[j-1].w==ge[j].w){ printf("第%d个最小生成树为:n",y); for(x=1;x<=j-2;x++){ printf("(%d,%d):%dn",xy[x].bv,xy[x].tv,xy[x].w); } printf("(%d,%d):%dn",ge[j].bv,ge[j].tv,ge[j].w); j++; y++; } } void insertsort(edgeset ge[],int e){//对权值进行排序 int i,j; for(i=2;i<=e;i++) if(ge[i].w ge[0]=ge[i]; j=i-1; while(ge[0].w ge[j+1]=ge[j]; j--; } ge[j+1]=ge[0]; } } main() { edgeset ge[MAXE]; int a,n,e,i; printf("请输入顶点个数:"); scanf("%d",&n); printf("请输入边的条数:"); scanf("%d",&e); printf("请输入边的信息(起点,终点,权值):n"); for(i=1;i<=e;i++) scanf("%d,%d,%d",&ge[i].bv,&ge[i].tv,&ge[i].w); printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); while(a!=2){ switch(a){ h h case 1:insertsort(ge,e); kruskal(ge,n,e); break; } printf("在下列菜单中进行选择:n"); printf("l算法((起点,终点)权值):n"); printf("(退出):n"); scanf("%d",&a); } return 1; } 资料仅供参考!!! h
发布评论