数据结构课程设计报告起评分理论成绩实践成绩总成绩院系:专业:班级:学号::教师:时间:目录一、设计要求 (3)1、问题描述 (3)2、功能 (3)3、数据 (3)二、概述与分析 (3)1、图 (3)2、邻接矩阵 (3)3、生成树 (4)4、最小生成树 (5)5、最小生成树的操作 (5)三、程序设计及分析 (6)四、流程图 (7)1、模块结构流程图 (7)2、Prim算法流程设计 (8)五、测试分析 (8)六、总结 (10)七、源程序代码 (10)一、设计要求:1、问题描述构造可以使n个城市连接的最小生成树。
2、功能给定一个地区的n个城市间的距离网,用Prim算法或Kruskal算法建立最小生成树,并计算得到的最小生成树的代价。
本人采用的是Prim算法。
3、数据城市间的距离网采用邻接矩阵表示(要求至少6个城市,10条边),邻接矩阵的存储结构定义采用课本中给出的定义,若两个城市之间不存在道路,则将相应边的权值设为自己定义的无穷大值。
要求在屏幕上显示得到的最小生成树中包括了哪些城市间的道路,并显示得到的最小生成树的代价。
表示城市间距离网的邻接矩阵(要求至少6个城市,10条边)二、概述与分析1、图图的定义:图G是有两个集合V和E组成,记做G=(V,E),其中V是定点的有限集合,记做V(G),E是连接V的两个不同的顶点的边的有限集合,记做E(G)。
2、邻接矩阵邻接矩阵是图的一种存储方法,它是表示顶点之间相邻关系的矩阵。
设G=(V,E)是具有n(n>0)个顶点的图,顶点的顺序依次为(v0,v1,…,vn-1),则G的邻接矩阵A是n阶方阵,其定义如下。
1)如果G是无向图,则1 (vi,vj) ∈E(G)A[i][j]=0 其他2)如果G是有向图,则1 <vi,vj> ∈E(G)A[i][j]=0其他3)如果G是带权无向图,则w ij vi≠vj且(vi,vj)∈E(G)A[i][j]= 0 vi =vj∞其他4)如果G是带权有向图,则w ij vi≠vj且<vi,vj> ∈E(G)A[i][j]= 0 vi =vj∞其他邻接矩阵的特点如下:1)图的邻接矩阵表示是唯一的。
2)无向图的邻接矩阵一定是一个对称矩阵。
因此,按照压缩存储的思想,在具体存放邻接矩阵是只需要存放上(或下)三角形阵的元素即可。
3)不带权的有向图的邻接矩阵一般是稀疏矩阵,因此,当图的顶点较多时,可以采用三元组表的方法存储邻接矩阵。
4)对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点Vi的度。
5)对于有向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点vi的出度(或入度)。
6)用邻接矩阵方法存储图,很容易确定图中任意两个顶点之间是否有边相连。
但是,要确定图中有多少条边,则必须按行和按列对每个元素进行检测,所花费的时间大家很大,这是用邻接矩阵存储图的局限性。
3、生成树一个连通图的生成树是一个极小连通图,它含有图中全部顶点,但只有构成一颗树的(n-1)条边。
如果在一颗生成树上添加一条边,必定构成一个环:因为这条边使得它依附的那两个顶点之间有了第2条路径。
一颗有n个顶点的生成树(连通无回路图)有且仅有(n-1)条边,则一定有回路。
但是,有(n-1)条边的图不一定都是生成树。
生成树的特点:1、连通且无环;2、包含原图中的全部n个顶点,但只有n-1条边;3、是原图的极小连通子图;4、最小生成树对于一个带权(假定每条边上的权值均大于零的实数)连通无向图G中的不同生成树,其每棵树的所有边上的权值之和也可能不同;图的所有生成树中的具有边上的权值之和最小的树成为图的最小生成树。
最小生成树的典型用途:欲在n个城市间建立通信网. n个城市可能有n(n-1)/2条线路,但铺n-1条线路即可连通;因为每条线路都会有对应的经济成本,那么,如何选择n–1条线路,使总费用最少?构造数学模型:顶点———表示城市,有n个;边————表示线路,有n–1条;边的权值—表示线路的经济代价;连通网——表示n个城市间通信网。
5、最小生成树的操作1、Prim算法:假设G=(V,E)是连通图,T=(U,TE)为欲构造的最小生成树,初始化U={u0},TE为空,重复以下操作:在所有u∈U,v∈V-U的边(u,v)∈E中,选择一条权最小的边(u,v)并入TE,同时将v并入U,直到U=V为止,此时T=(U,TE)是G的一棵最小生成树。
2、在prim算法中需要解决的两个问题:1)在无向网中,当出现从一个顶点到其它顶点时,若其边上的权值相等,则可能从某一起点出发时会得出不同的最小生成树,但最小生成树的权必定都相等,此时我们应该怎样将所有的最小生成树求解出来;2)每次如何从生成树T中到T外的所有边中,找出一条最短边。
例如,在第k次(1≤k≤n-1)前,生成树T中已有k个顶点和k-1条边,此时T中到T外的所有边数为k(n-k),当然它也包括两顶点间没有直接边相联,其权值被看做常量INF的边在,从如此多的边中查找最短边,其时间复杂度为O(k(n-k)),显然是很费时的,是否有一种好的方法能够降低查找最短边的时间复杂度。
三、程序设计及分析定义结构体和常量#define MAXV 30#define INF 32767 //INF 表示无穷大struct Node{int weight;int vex;//存放顶点编号int lowcost;//存放顶点权值};struct MGraph{int vexs[MAXV],n,e; //vexs表示顶点向量;n,e分别表示图的顶点数和边数Node city[MAXV][MAXV]; //邻接矩阵};Node closest[MAXV];// 存放每个顶点所连接的边所对应的权值1. 函数功能int Locate(MGraph &g,int V) //求顶点位置函数MGraph CreateMap(MGraph &g) //创建图在其函数体中,首先通过键盘输入网中顶点数,若顶点个数<=1时,显示“最小生成树不存在”,然后返回主调函数;否则,继续通过键盘输入网中的边数,通过二重for循环初始化邻接矩阵,然后输入各个顶点的编号及每条边的权值。
int Min(MGraph &g,Node closest[])//closest[]中权值最小的边void prim(MGraph &g,int u)// 从顶点u出发,按普里姆算法构造连通图g的最小生成树,并输出生成树的每条边定义一个p用于存放最小生成树中的顶点(按生成最小生成树时的顺序存放),调用函数Locate()求出起点u在顶点向量表中的位置,将u存放到p的顶点向量表中,初始化初始化U={u},利用for循环对V-U中的顶点i,初始化closest[i],再利用for循环找n-1条边(n=g.n),其中,调用函数Min()求出辅助数组closest[]中权值最小的边, 并将其在辅助数组中的相应位置返回到主调函数中并赋给k0,此时closest[k0]中存有当前最小边(u0, v0)的信息,将边所对应的终点放入p的顶点向量表中,累加边的权值;然后,输出;最后,在顶点v0并入U之后,利用for循环更新closest[i];当所有的顶点v0都纳入U 集合之后,输出最小生成树的权值之和。
四、流程图1、模块结构流程图2、Prim算法流程设计如图六个城市之间共有10条路,运用Prim算法:选定第一个点①,从它closest的边中选择最短边①---②,权值为1;从①②的closest的边中选择最短边①---⑥,权值为2;从①②⑥的closest的边中选择最短边⑥---③,权值为3;从①②⑥③的closest的边中选择最短边③---④,权值为2;从①②⑥③④的closest的边中选择最短边④---⑤,权值为4所得最小生成树的权值为1+2+3+2+4=12.① 1 ②① 1 ②2 8 ⑤10 6 7 2 45 4 3 ⑥③ 2 ④③ 2 ④五、测试分析系统运行后出现的界面:根据流程图模型输入相应的数字进行调试检验:六、总结这个课程设计参考了《数据结构(C语言版)》(清华大学),在运行过程中由于prim函数中没有定义p来存放最小生成树中的顶点,导致结果部分会有问题,经过改进后就避免了此类问题。
七、源程序代码#include<iostream.h>#include<stdio.h>#include<string.h>#include<malloc.h>const int MAXV=30; //最多顶点个数const int INF=32768;//表示极大值,即∞struct Node{int weight;int vex;//存放顶点编号int lowcost;//存放顶点权值};struct MGraph{int vexs[MAXV],n,e; //vexs表示顶点向量;n,e分别表示图的顶点数和边数Node city[MAXV][MAXV]; //邻接矩阵};Node closest[MAXV];//求最小生成树时的辅助数组int Locate(MGraph &g,int V) //求顶点位置函数{int j=-1,k;for(k=0;k<g.n;k++)if(g.vexs[k]==V){j=k;break;}return j;}MGraph CreateMap(MGraph &g) //创建图{int i,j,k,v1,v2,vex,m=1;cout<<"请输入城市的个数:";cin>>g.n;if(g.n<=1){cout<<"最小生成树不存在!"<<endl;return g;}else{cout<<"请输入城市的边数:";cin>>g.e;for(i=0;i<g.n;i++) //初始化邻接矩阵for(j=0;j<g.n;j++)if(i==j)g.city[i][j].weight=0;elseg.city[i][j].weight=INF;cout<<"请输入城市的顶点编号:"; //输入图中的顶点编号for(i=0;i<g.n;i++)cin>>g.vexs[i];cout<<"输入每条边所对应的两个顶点及权值<格式:起点城市终点城市权值>!"<<endl;for(k=0;k<g.e;k++){cout<<"请输入第"<<m++<<"条边:";cin>>v1>>v2>>vex; //输入一条边的两个顶点及权值i=Locate(g,v1);j=Locate(g,v2);g.city[i][j].weight=vex;g.city[j][i].weight=vex;}return(g);}}int Min(MGraph &g,Node closest[])//closest[]中权值最小的边{int i,min,j;min=INF;for(i=0;i<g.n;i++)if(closest[i].lowcost<min&&closest[i].lowcost!=0){min=closest[i].lowcost;j=i;}return j;//返回权值最小的边在辅助数组中的位置}void prim(MGraph &g,int u)//从顶点u出发,按普里姆算法构造连通图g的最小生成树,并输出生成树的每条边{MGraph p;int i,j,k,k0,u0,v0,s=0,n=0;k=Locate(g,u);//k为顶点u的位置p.vexs[n++]=u;closest[k].lowcost=0;//初始化U={u}for(i=0;i<g.n;i++)if(i!=k) //初始化closest[i]{closest[i].vex=u;closest[i].lowcost=g.city[k][i].weight;}for(j=1;j<=g.n-1;j++)//选择其余的n-1条边(n=g.n){k0=Min(g,closest);//closest[k0]中存有当前最小边(u0, v0)的信息u0=closest[k0].vex;//u0∈Uv0=g.vexs[k0]; //v0∈V-Up.vexs[n++]=v0;s+=closest[k0].lowcost;//求最小生成树的权值之和 cout<<"从编号为 "<<u0<<" 的城市到编号为 "<<v0<<" 的城市"<<"的权值为"<<closest[k0].lowcost<<endl; //输出最小生成树的路径 closest[k0].lowcost=0;//将顶点v0纳入U集合for(i=0;i<g.n;i++)//在顶点v0并入U之后,重新选择最小边 if(g.city[k0][i].weight<closest[i].lowcost){closest[i].lowcost=g.city[k0][i].weight;closest[i].vex=v0;}}cout<<"\n最小生成树的权值之和为:"<<s<<endl;}void display(MGraph &g) //输出邻接矩阵算法{int i,j;for(i=0;i<g.n;i++){for(j=0;j<g.n;j++)if(g.city[i][j].weight==INF)cout<<"\t"<<"∞";elsecout<<"\t"<<g.city[i][j].weight;cout<<endl;}}void main() //主函数{int st;MGraph g;cout<<"\n*********************n个城市的最小生成树*****************"<<endl;CreateMap(g);{cout<<"\n该图所对应的邻接矩阵如下:"<<endl;display(g);cout<<endl<<"请输入起点城市编号:";cin>>st;while(1){if(st==0) break;else if(st>g.n)cout<<"输入起点城市编号有错,请重新输入!"<<endl;else{prim(g,st);cout<<"\n*************************************************"<<endl;}cout<<endl<<"请继续输入起点城市,否则输入0退出!"<<endl;cin>>st;}}}。