数据结构课程设计报告.docx
《数据结构课程设计报告.docx》由会员分享,可在线阅读,更多相关《数据结构课程设计报告.docx(22页珍藏版)》请在冰豆网上搜索。
数据结构课程设计报告
数据结构程序设计
课程设计(报告)
题目:
最短路径
专业班级:
GIS
学生姓名:
火艇
学号:
1000000
完成时间:
2012年6月28日
目录
一、需求分析2
1.1前言2
1.2需求分析2
1.2.1数据需求分析2
1.2.2最短路径问题的算法模型需求分析3
1.2.3功能需求分析5
二、概要设计5
三、详细设计6
四、调试分析9
4.1程序调试9
4.2程序异常处理12
4.3心得与体会13
五、用户使用说明14
六、测试结果14
七、附录或参考资料15
一、参考文献:
15
二、附录源代码15
八、成绩20
一、需求分析
1.1前言
数据结构是基于对具体问题抽象出一个适当的数学模型,然后设计一个解此数学模型的算法,最后编写程序,进行测试、调整直至得到最终答案。
寻求数学模型的实质是分析问题,有助于开发者基于此分析问题并解决问题。
本课程设计,设计和实现了在带权值的有向图中,如何寻找图中的最短路径的算法,通过弗洛伊德算法,按路径长短递增的次序,不断比较从某一点出发到其他顶点的权值,并使之最小。
通过C#语言实现这个程序,可以高效率地找出带权值的图的最短路径。
本文主要介绍了本课题的开发背景,所要完成的功能和设计的过程。
重点说明了利用弗洛伊德算法确定带权值的有向图的最短路径算法的需求分析、概要设计、详细设计、调试分析各个过程的设计与实现。
1.2需求分析
交通网络可以化成带权的图,图中顶点表示城市,边代表城市间的公路,边上的权表示公路的长度。
这样可以发现两个地点之间有无公路可连,在几条公路可通的情况下,可以找到那条路径最短。
在本次实验中,我将利用弗洛伊德算法法来进行程序设计。
通过按路径长短递增的次序产生最短路径,通过邻接矩阵来实现有路径的顶点之间权值的表示,依次求出最短路径。
1.2.1数据需求分析
背景:
最短路径问题是图论算法研究中一个经典问题,旨在寻找图中两点之间的最短路径。
算法具体的形式包括:
1.确定起点的最短路径问题:
即一直起始结点,求最短路径的问题;
2.确定终点的最短路径问题:
与确定起点问题相反,该问题是一直终结点,求最短路径的问题。
在无向图中该问题与确定起点的问题完全等同,在有向图中该问题等同于把所有路径方向反转的确定起点的问题;
3.确定起点终点的最短路径问题:
即已知起点和终点求两结点之间的最短路径;
4.全局最短路径问题:
求图中所有的最短路径,即任意两点之间的最短路径。
应用:
求最短路径在现实中有着广泛的应用。
比如:
管道铺设、线路安排。
实例模型:
给定一个n个顶点的图,以及相连通顶点之间的权值,给定两点A、B(A、B分别为起点和终点,后文同),求AB的最短距离,以及输出从A到B的最短路径。
1.2.2最短路径问题的算法模型需求分析
最短路径问题又可以归结为两类问题:
一、单源点最短路径问题;二、每一对顶点之间的最短路径
一、单源点最短路径问题
问题描述:
给定带权有向图G=(V,E)和源点v∈V,求从v到G中其余各顶点的最短路径。
迪杰斯特拉(Dijkstra)提出了一个按路径长度递增的次序产生最短路径的算法。
Dijkstra(迪杰斯特拉)算法:
基本思想:
设置一个集合S存放已经找到最短路径的顶点,S的初始状态只包含源点v,对vi∈V-S,假设从源点v到vi的有向边为最短路径。
以后每求得一条最短路径v,…,vk,就将vk加入集合S中,并将路径v,…,vk,vi与原来的假设相比较,取路径长度较小者为最短路径。
重复上述过程,直到集合V中全部顶点加入到集合S中。
下一条最短路径(设其终点为vi)或者是弧(v0,vi),或者是中间经过S中的顶点而最后到达顶点vi的路径。
示例:
算法步骤:
①令S={Vs},用带权的邻接矩阵表示有向图,对图中每个顶点Vi按以下原则置初值:
②选择一个顶点Vj,使得:
dist[j]=Min{dist[k]|Vk∈V-S},Vj就是求得的下一条最短路径终点,将Vj并入到S中,即S=S∪{Vj}。
③对V-S中的每个顶点Vk,修改dist[k],方法是:
若dist[j]+Wjkdist[k]=dist[j]+Wjk("Vk∈V-S)
④重复②,③,直到S=V为止。
二、每一对顶点之间的最短路径
问题描述:
给定带权有向图G=(V,E),对任意顶点vi,vj∈V(i≠j),求顶点vi到顶点vj的最短路径。
解决办法1:
每次以一个顶点为源点,调用Dijkstra算法n次。
显然,时间复杂度为O(n3)。
解决办法2:
弗洛伊德提出的求每一对顶点之间的最短路径算法——Floyd算法,其时间复杂度也是O(n3),但形式上要简单些。
Floyd(弗洛伊德)算法:
基本思想:
对于从vi到vj的弧,进行n次试探:
首先考虑路径vi,v0,vj是否存在,如果存在,则比较vi,vj和vi,v0,vj的路径长度,取较短者为从vi到vj的中间顶点的序号不大于0的最短路径。
在路径上再增加一个顶点v1,依此类推,在经过n次比较后,最后求得的必是从顶点vi到顶点vj的最短路径。
示例:
数据结构:
图的存储结构:
带权的邻接矩阵存储结构
数组dist[n][n]:
存放在迭代过程中求得的最短路径长度。
迭代公式:
数组path[n][n]:
存放从vi到vj的最短路径,初始为path[i][j]="vivj"。
1.2.3功能需求分析
本系统主要实现对从有向网中的某一顶点出发找到该顶点到其余各顶点的最段路径。
对邻接矩阵cost[n][n]中的每一个元素只能有三种情况:
(1)当i=j时,cost[i][j]=0;
(2)当顶点i到顶点j无边时,cost[i][j]=0;
(3)当顶点i到j有边且权值为W时,cost[i][j]=W
二、概要设计
对于n个顶点的图,求每一对顶点间的最短路径,常用的有两种方法:
一是按Dijkstra(迪杰斯特拉)算法,求每一个顶点到其它各顶点间的最短路径,再加一层循环,时间复杂度是O(n^3),另一种方法是Floyd(弗洛伊德)提出来的,时间复杂度也是O(n^3)。
通过上术1.2.2,我们很明显可以看出Floyd算法形式上更简单。
当然两种方法各有春秋,Floyd虽然形式简单,但是要输出路径,处理起来就比较麻烦了。
把顶点分成两组,第一组是已确定最短路径的结点的集合,第二组是尚未确定最短路径的结点的集合。
按路径长度递增的次序逐个把第二组的顶点放到第一组中。
设求从v1到其它各顶点间的最短路径,则在任意时刻,从v1到第一组各顶点间的最短路径都不大于从v1到第二组各顶点间的最短路径。
三、详细设计
用Floyd求最短路径的具体步骤如下:
弗洛伊德(Floyed)提出了的用于计算有向网中所有顶点的最短路径,这种算法称为弗洛伊德算法,它的时间复杂度为O(n的3次方)。
弗洛伊德算法仍然使用邻接矩阵存储的图,同时定义了一个二维数组A,其每一个分量A[I,j]是顶点i到顶点j的最短路径长度。
另外它还使用了另一个二维数组path来保存最短路径信息。
弗洛伊德算法的基本思想如下:
1.初始时,对图中任意两个顶点Vi和Vj,如果从Vi到Vj存在边,则从Vi到Vj存在一条长度为cost[i,j]的路径,该路径不一定是最短路径。
初始化时,A[i,j]=cost[i,j]。
protectedstaticvoidFloyd(double[,]cost)
{intn=cost.GetLength
(1);//图中顶点个数
double[,]saveshortpath=newdouble[n,n];//存放最短路径
double[,]pathInfo=newdouble[n,n];//存放最短路径信息
for(inti=0;i{
for(intj=0;j{//辅助数组saveshortpath和pathInfo的初始化
saveshortpath[i,j]=cost[i,j];
pathInfo[i,j]=-1;
}
}
。
。
。
}
2.在图中任意两个顶点Vi和Vj之间加入顶点Vk,如果Vi经Vk到达的路径存在并更短,则用A[i,k]+A[k,j]的值代替原来的A[i,j]值。
for(intk=0;k{
for(inti=0;i{
for(intj=0;j{//如果存在通过中间点k的路径
if(i!
=j&&saveshortpath[i,k]!
=0&&saveshortpath[k,j]!
=0)
{//如果加入中间点k后的路径更短
if(saveshortpath[i,j]==0||saveshortpath[i,j]>saveshortpath[i,k]+saveshortpath[k,j])
{//用新路径代替原路径
saveshortpath[i,j]=saveshortpath[i,k]+saveshortpath[k,j];
pathInfo[i,j]=k;
}
}
}
}
}
3.重复步骤
(2),直到将所有顶点作为中间点依次加入集合中,并通过迭代公式不断修正A[i,j]的值,最终得任意顶点间的最短路径长度。
图5.1.1有向图实例模型
//打印最短路径及路径长度
for(inti=0;i{
for(intj=0;j{
if(saveshortpath[i,j]==0)
{
if(i!
=j)
{
Console.WriteLine("从V{0}到V{1}\t\t\t\t没有路径",i,j);
}
}
else
{
Console.Write("从V{0}到V{1}的路径为:
",i,j);
Console.Write("V{0}"+"→",i);
GetPath(pathInfo,i,j);
Console.Write("V{0}",j);
Console.WriteLine("\t\t\t最短路径为:
"+saveshortpath[i,j]);
}
}
}
}
//使用递归获取指定顶点的路径
protectedstaticvoidGetPath(double[,]pathInfo,doublei,doublej)
{
doublek=pathInfo[Convert.ToInt32(i),Convert.ToInt32(j)];
if(k==-1)
{
return;
}
//递归算法
GetPath(pathInfo,i,k);
Console.Write("V{0}"+"→",k);
GetPath(pathInfo,k,j);
}
输出的实现:
Floyd中pathInfo[i]已经记录了从A到达i经过的各个点,但是它们并没有先后顺序,所以我们还得结合各点的连通关系,即要结合邻接矩阵Graph。
当到达点i时,我们搜索与i连接的点j,当pathInfo[i][j]==true时,则说明j是该路径中i之后的经过的点。
有向图时,可以直接用这两个条件就可以了,但是无向图时,Graph[i][j]和Graph[j][i]都是小于MAXINT的,即i和j都是连通的,如果只用上述两个条件,i可能会被重复输出,因此,要加一个标志数组,防止已经输出的点再被重复输出。
因为dist[i]是表示A到i的最短路径,初始值为MAXINT,当dist[i]最后还为MAXINT,则A无法到达点i。
所以输出从点A往下逐个搜,直到到达B。
输出方法
//解算结果
protectedstaticvoidGetResult()
{
Console.WriteLine("数据结构课程设计(最短路径问题解决方案)\n使用弗洛伊德算法求两点间的最短距离,请您根据提示输入:
\n***********************************************\n\n");
Console.Write("请您输入顶点个数:
");
intr=ConInt(Console.ReadLine());//顶点个数
while(r==-1)
{
Console.Write("请您重新输入顶点个数:
");
r=ConInt(Console.ReadLine());
}
Console.Write("您输入的各点分别为:
\n");
for(inti=0;iConsole.Write("V{0}",i);
Console.WriteLine("\n\n输入各点之间的距离,建立有向网\n");
double[,]cost=newdouble[r,r];
doubleda;
for(inti=0;i{//手动输入各点权值
for(intj=0;j{
if(i!
=j)
{
Console.Write("V{0}到V{1}的距离是:
",i,j);
da=ConDouble(Console.ReadLine());
while(da==-1)
{
Console.Write("请重新输入V{0}到V{1}的距离:
",i,j);
da=ConDouble(Console.ReadLine());
}
cost[i,j]=da;
Console.WriteLine();
}
}
}
Console.WriteLine("信息输入完毕,请按Enter键确定输出各顶点间的最短路径:
");
Console.ReadKey();
Floyd(cost);//使用弗洛伊德算法求解所有顶点间的最短路径
}
四、调试分析
4.1程序调试
1.从屏幕上输入顶点的个数:
2.输入顶点个数后按回车键确定,显示出各个顶点以0为开始分别为V0、V1、V2、V3......之后,输入各点间的距离(0或空表示两点间不存在路径)。
3.依次输入各顶点间的距离
4.按回车键确定,所要求的结果已出来,如果继续可以输入y:
4.2程序异常处理
在实际的软件项目开发中,不论程序员是久经沙场的老将,还是接触语言的新手,出现Bug是必然的,我们如何才能做到系统在运行时遇到错误而不致使系统崩溃呢,我们需要在系统编码中定制异常处理。
在软件项目开发中,项目的成功与失败的一个很关键的因素就是项目的质量。
如何确保整个项目的质量,首先我们需要确保项目中的某一个模块以及某个类、某一个方法的质量。
这就是说“牵一发而动全身”,尽管是一个简单的方法、一个简单的类还是一个简单的程序我们都应该考虑其异常情况,并进行异常处理。
即预知可能发生的特殊情况,并在程序编码中处理这些特殊情况。
所谓异常处理就是,当我们的应用系统在运行时发生错误后,不论这种错误是由哪种原因引起的,通过开发人员对预知错误的处理或CLR内部的处理,使系统能够恢复运行的过程。
“异常”是程序在执行时发生的错误。
导致这种错误的原因来自三个方面。
(1)代码错误,包括语法错误、逻辑错误,这是由开发人员造成的。
(2)资源不可用,这是由系统访问了XX的资源而引起的错误。
(3)公共语言运行库,这是由CLR内部引起的错误。
1.用户在输入顶点个数时输入非数值类型或非整数,或输入顶点间距离非值类型
2.用户输入顶点个数为0或1时(虽然这不算是异常,但是顶点个数0或1不存时并不存在路径,所以就不用进行下面的计算,直接抛出“路径不存在”。
这样做的目的是为了,避免无用的计算,提高效率。
)
4.3心得与体会
这次课程设计虽然做的仅是一个小小程序,但我深刻体会到在实际开发中我们这些开发者追求的目标不仅仅是为了解决实际问题的目的,更重要的是解决问题的质量。
解决问题的途径和方法有很多,有复杂、有简单的、有效率低的,有效率高的。
。
。
但作为开发者不能仅考虑其中一方面或者几方面,而是要综合考虑。
有时候程序的效率很高,但代码复杂,就不便日后项目或系统维护;有时候欲速则不达用,小聪明、权宜之计解决问题,求快而不顾代码质量,往往给项目留下要命的死角;有时候一个看起来无足轻重的代码,却是最要命的漏洞。
通过一周多的上机操作,自己对数据结构有了更进一步的理解,但是仍然缺乏有很多基础知识,数据结构里面的算法其基本上能弄懂,可要将其思想换成C#代码在VS中运行就有难度,如果再进一步有用算法思想去解决实际问题就更难了。
不过通过这周课程设计,我更深刻体会到学习一门语言不能仅仅停留在书本上,更重要的是付诸实践。
实践才是检验真理的唯一标准,光看书,收益不大,动手操作才学习计算机语言的利剑。
花一个月去苦读书籍也许效果还不如动手敲几行代码。
因为实际应用中因为不同的情况会出现很多不同的问题,而我们书本上不一定提到,毕竟书本是死的人是活的。
同一个问题不同的情况,很可能有不同的解决办法,如果生搬硬套的话很容易张冠李戴。
当然书本知识很重要,理论毕竟是实践的基础,缺乏理论,仅凭经验容易落伍,因为技术在不断发展,高手大有人在。
五、用户使用说明
一、最短路径分为从某个源点到其余各顶点的最短路径和每一对顶点之间的最短路径,最短路径问题也是地理信息系统网络分析中的一个关键内容,目前在GIS中,特别是在交通、电力等GIS应用系统中,最短路径算法的优化和实现已经成为整个系统的核心功能之一。
如下:
1.能够输入图的顶点和弧的信息,建立有向网;
2.能求从某个源点到其余各顶点的最短路径;
3.能求每一对顶点之间的最短路径。
二、本程序功能以及使用
1.输入顶点数,将会在控制台打印有向网的各顶点,再输入各顶点间距离后就能输出各点间的最短路径,以及最短路径值;
2.输入的顶点数、距离值必须为正整数,如果不是数值或者是不是正整数,将会提示“对不起,您输入非法字符或非正整数!
”,并提示“请您重新输入顶点个数:
”;
3.如果输入的顶点数为0或1,将会提示“对不起,0个或1个顶点不存在路径!
”,并会让您重新输入顶点数;
4.各顶点间的距离允许为空,空值默认为0,即两点间不存在路径;
5.个顶点间距离输入完毕,将会提示“信息输入完毕,请按Enter键确定输出各顶点间的最短路径:
”,如没有路经会显示“没有路径”,存在路径即会显示各路径经过的顶点以及最短路径值。
6.输入顶点数→Enter→输入各顶点间距离→Enter→显示结果→输入y继续,否则退出
六、测试结果
七、附录或参考资料
一、参考文献:
【1】:
陈优良《数据结构C#》江西理工大学大学出版社
【2】:
c#入门经典(第三版)清华大学出版社
【3】:
陈广《数据结构C#》北京大学出版社
二、附录源代码
usingSystem;
usingSystem.Collections.Generic;
usingSystem.Linq;
usingSystem.Text;
namespaceShortestPath
{
classProgram
{
//弗洛伊德算法
protectedstaticvoidFloyd(double[,]cost)
{
intn=cost.GetLength
(1);//图中顶点个数
double[,]saveshortpath=newdouble[n,n];//存放最短路径
double[,]pathInfo=newdouble[n,n];//存放最短路径信息
for(inti=0;i{
for(intj=0;j{//辅助数组saveshortpath和pathInfo的初始化
saveshortpath[i,j]=cost[i,j];
pathInfo[i,j]=-1;
}
}
//弗洛伊德算法核心代码
for(intk=0;k{
for(inti=0;i{
for(intj=0;j{
//如果存在通过中间点k的路径
if(i!
=j&&saveshortpath[i,k]!
=0&&saveshortpath[k,j]!
=0)
{//如果加入中间点k后的路径更短
if(saveshortpath[i,j]==0||saveshortpath[i,j]>saveshortpath[i,k]+saveshortpath[k,j])
{
//用新路径代替原路径
saveshortpath[i,j]=saveshortpath[i,k]+saveshortpath[k,j];
pathInfo[i,j]=k;
}
}
}
}
}
//打印最短路径及路径长度
for(inti=0;i{
for(intj=0;j{
if(saveshortpath[i,j]==0)
{
if(i!
=j)
{
Console.WriteLine("从V{0}到V{1}\t\t\t\t没有路径",i,j);
}
}
else
{
Console.Write("从V{0}到V{1}的路径为:
",i,j);
Console.Write("V{0}"+"→",i);
GetPath(pathInfo,i,j);
Console.Write("V{0}",j);
Console.WriteLine("\t\t\t最短路径为:
"+saveshortpath[i,j]);
}
}
}
}
//使用递归获取指定顶点的路径
protectedstaticvoidGetPath(double[,]pathInfo,doublei,doublej)
{
doublek=pathInfo[Convert.ToInt32(i),Convert.ToInt32(j)];
if(k==-1)
{
return;
}
//递归算法
GetPath(pathInfo,i,k);
Console.Write("V{0}"+"→",k);
GetP