一、课程设计名称及内容
名称:Floyd算法的实现
内容:对任意图,选择合适的数据结构表示图,在此基础上实现求解最短路径的Floyd算法。二、任务和要求
任务:⑴ 通过独立解决某个课程设计问题,在数据结构的逻辑特性和物理表示、数据结构的选择应用、算法的设计及其实现等方面加深对课程基本内容的理解和综合运用。
⑵ 深刻理解、牢固掌握数据结构和算法设计技术,提高分析和解决实际问题的能力。
⑶ 在程序设计方法以及上机操作等基本技能和科学作风方面进行比较系统和严格的训练。
要求:按“课程设计教学大纲”的要求完成“数据结构与算法课程设计报告”。
三、问题分析
对于任意图,选择存储结构存储图并实现FLOYED算法求解最短路径。将问题分解,分解为两个方面。一是对于任意图的存储问题,第二个是实现floyed算法求解最短路径。首先对于图的创建选择合适的存储结构进行存储,对于合适的存储结构可以简化程序。本实验采用邻接矩阵存储。然后是实现FLOYED算法求解最短路径,在FLOYED算法中路径的长度即是图中两顶点间边的权值,FLOYED算法要求输出任意两个顶点间的最短路径,而且经过的顶点也要输出。考虑到问题的特殊性,采用两个二维数组进行存储。第一个二维数组存储最短路径,第二个二维数组存储路径经过的顶点,在进行适当的运算后对这两个数组进行输出即可。通过问题的分解,逐个解决,实现所要求程序。
三、概要设计
为实现上述程序的功能,需要创建邻接矩阵存储图,FLOYED算法求解最短路径。在求解最短路径的时候需要申请两个二维数组A[][]和path[][]分别存储路径和路径经过的顶点。输出是需判断A[][]的值,若A[i][j]=0但i!=j,此时i到j的路径长度就是0,若A[i][j]=INF,及最大值,表示从i到j没有路径,输出路径的同时输出经过的顶点即可。
本程序包含个函数
1.主函数main()
2.邻接矩阵创建函数create()
3.FLOYED算法函数floyed()
4.输出函数dispath()
5.前递归输出函数ppath()
各函数间关系如下:
main |
create |
floyed |
dispath |
ppath |
图1主函数及个函数间关系
四、详细设计
对于任意图,选择存储结构存储图并实现FLOYED算法求解最短路径。由课程设计题目,设计思想如下:
对于图,可采用邻接矩阵和邻接表存储,本程序采用邻接矩阵存储。
假设G={V,E}是一个有n个顶点的图,我们规定个顶点的序号依次为1,2,3,……,n,则G的邻接矩阵是一个具有如下定义的n阶方阵:
A[i,j]=1,若<Vi,Vj>或者(Vi,Vj)∈E(G)
A[i,j]=0,反之
对于在边上附有权值的网,可以将以上的定义修正为:
A[i,j]=Wi, 若<Vi,Vj>或者(Vi,Vj)∈E(G)
A[i,j]=0, 反之
其中Wi表示<Vi,Vj>弧或者(Vi,Vj)边上的权值。
一个图的邻接矩阵存储结构可以用两个数组来表示。其中第一个数组vexs是一维数组,用来存储途中顶点的信息;另外一个二维数组edges,用来存储途中边或者弧的信息。邻接矩阵数据类型如下:
typedef struct{
int data;//顶点信息
int num;//顶点序号
}vertex;
typedef struct{
int n;//顶点个数
int e;//边个数
vertex vexs[ma];//存储顶点
int edges[ma][ma];//存储边的权值
}mgraph;//邻接矩阵存储图
在图的初始化过程中,若两顶点无直接路径的话,就用自定义最大变量INF9999来表示。
如上就解决了图的存储问题。下面就FLOYED算法求解最短路径给出设计思想如下:
如果有一个矩阵D=[A(ij)],其中A(i,j)表示i顶点到j顶点的距离。若i与j之间无路可通,那么A(i,j)就是无穷大,本程序用自定义的一个最大数9999表示。又有A(i,i)=0,若i=j,则是顶点,无需考虑,若i!=j,则表示这两点间的路径长度为0.编写一个程序,通过这个距离矩阵D,把任意两个点之间的最短与其行径的路径找出来。
我们可以将问题分解,先找出最短的距离,然后在考虑如何找出对应的行进路线。如何找出最短路径呢,这里用到动态规划的知识,对于任何一个点而言,i到j的最短距离不外乎存在经过i与j之间的k和不经过k两种可能,所以可以令k=1,2,3,...,n(n是点的数目),在检查A(i,j)与A(i,k)+A(k,j)的值;在此A(i,k)与A(k,j)分别是目前为止所知道的i到k与k到j的最短距离,因此A(i,k)+A(k,j)就是i到j经过k的最短距离。所以,若有A(i,j)>A(i,k)+A(k,j),就表示从i出发经过k再到j的距离要比原来的i到j距离短,这样把i到j的A(i,j)通过赋值运算A(i,j)=A(i,k)+A(k,j),每当一个k查完了,A(i,j)就是目前的i到j的最短距离。重复这一过程,最后当查完所有的k时,A(ij)里面存放的就是i到j之间的最短距离了。所以我们就可以用三个for循环把问题完成:
for(k=1; k<=g->n; k++)
for(i=1; i<=g->n; i++)
for(j=1; j<=g->n; j++)
接下来就是如何找出最短路径所经过的点,这里要用到另一个矩阵path,它的定义是这样的:path(i,j)的值如果为p,就表示i到j的最短行经为i->...->p->j,也就是说p是i到j的最短行径中的j之前的最后一个点。path矩阵的初值为path(i,j)=-1。对于i到j而言找出path(i,j),令为p,就知道了路径i->...->p->j;再去找path(i,p),如果值为q,i到p的最短路径为i->...->q->p;再去找path(i,q),如果值为r,i到q的最短路径为i->...->r->q->p;所以一再反复,到了某个path(i,t)的值为-1时,就表示i到t的最短路径为i->t,即是到终点,则i到j的最短行径为i->t->...->q->p->j。
因为上述的算法是从终点到起点的顺序找出来的,所以输出的时候要把它倒过来。利用向前递归的算法实现,思想如下:
void ppath(int path[][ma], int i, int j)
{//向前递归查找路径上的顶点
int k;
k=path[i][j];
if(k==-1) return;
ppath(path,i,k);
printf("%d->",k);
ppath(path,k,j);
}
所经过的点如何保存问题,解决思想如下:
在判断最短路径的时候,如过当前的路径比经过某几个顶点后的路径要长的话,对当前的路径进行修改,并保存下经过的顶点,用矩阵path来存储。实现如下:
for(k=1; k<=g->n; k++){
for(i=1; i<=g->n; i++)
for(j=1; j<=g->n; j++)
if(A[i][k]!=0 && A[k][j]!=0&&A[i][j]>(A[i][k]+A[k][j])){
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;
}
输出最短路径可调用输出函数来实现,本程序中用dispath函数来实现输出模块。
void dispath(int A[][ma],int path[][ma],int n)//输出所有顶点最短路径
{
int i,j;
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
if(A[i][j]==INF){
if(i!=j)
printf("从%d到%d没有路径\n",i,j);}
else if(A[i][j]==0){
if(i!=j)
{printf("从%d到%d的最短路径为:",i,j);
printf("%d->",i);
ppath(path,i,j);
printf("%d",j);
printf("\t路径长度为:%d\n",A[i][j]);}}
else{
printf("从%d到%d的最短路径为:",i,j);
printf("%d->",i);
ppath(path,i,j);
printf("%d",j);
printf("\t路径长度为:%d\n",A[i][j]);
}
}在输出模块调用前递归查找函数,输出经过的顶点。
如上就实现了本程序要求的所用功能,对于任意图采用存储结构存储,并实现FLOYED算法求解最短路径。
五、用户使用说明及测试
程序名为floyef.c,运行环境为DOS。程序执行后显示
初始化图,请输入顶点个数和边的个数:输入4 12
输入顶点信息,顶点序号:输入1 2 3 4
输入边的信息,权值,相邻顶点有边,若不连接权值为9999
输入<1 2> 16 <1 3> 15
<1 4> 9999 <2 1> 29
<2 3> 9999 <2 4>13
<3 1> 21 <3 2> 9999
<3 4> 7 <4 1> 9999
<4 2> 27 <4 3> 19
1 |
2 |
3 |
4 |
16 |
29 |
13 |
19 |
21 |
27 |
7 |
15 |
图2 有向图
程序运行输出
得到的邻接矩阵如下:
0 16 15 9999
29 0 9999 13
21 9999 0 7
9999 27 19 0
输出最短路径:
从1到2的最短路径为:1->2 路径长度为:16
从1到3的最短路径为:1->3 路径长度为:15
从1到4的最短路径为:1->3->4 路径长度为:22
从2到1的最短路径为:2->1 路径长度为:29
从2到3的最短路径为:2->4->3 路径长度为:32
从2到4的最短路径为:2->4 路径长度为:13
从3到1的最短路径为:3->1 路径长度为:21
从3到2的最短路径为:3->4->2 路径长度为:34
从3到4的最短路径为:3->4 路径长度为:7
从4到1的最短路径为:4->3->1 路径长度为:40
从4到2的最短路径为:4->2 路径长度为:27
从4到3的最短路径为:4->3 路径长度为:19
六、总结
通过floyed算法求解图中顶点的最短路径问题实验,更加深入的了解了数据结构与算法在各个领域的应用。比如图的最短路径问题,衍生为校内导游图,乘车路径问题等等的实际性的日常问题。
在该实验的求解过程中,把问题分解为三个部分来解决。首先考虑的是对于任意图的存储问题。在数据结构与算法中,介绍了多种图的存储结构,有邻接矩阵存储,邻接表存储以及十字链表等等存储方法。考虑到本实验要对图进行求解路径,采用邻接矩阵存储。邻接矩阵存储图很容易判断图中两个顶点是否相连。例如,判断Vi和Vj是否相连,只需检查mg->edges[i][j]是否等于0,如果等于则不相连,否则相连。以上是两个顶点是否直接相连的算法。
然后是路径问题。对于任意两个顶点,路径也就分为三种情况,即直接相连是最短路径,经过某几个顶点后,经过的路径是最短路径,还有这两个顶点之间没有最短路径。对于直接相连的路径就是最短路径,直接输出即可。例如a->b直接相连,且最短路径就是直接路径,路径长度为10,输出a到b得路径为:a->b,路径长度为10。经过某几个顶点后的路径是最短路径,用二维数组A[][]先保存最短路径长度。若A[i][j]>A[i][k]+A[k][j],则经过k点后i到j得的路径短一些,此时将A[i][k]+A[k][j]赋值给A[i][j],用二维数组path[][]记录下经过的点k,这样在循环里对整个路径都进行一遍比较,保存下最短路径以及这些路径经过的顶点。对于两点之间没有路径的问题,A[i][j]一直为最大值9999,若有路径则同第二步,没有则A[i][j]一直保持不变。
最后待解决的问题就是输出任意两点之间的最短路径。从i到j得最短路径长度全部保存在二维数组A[][]中,要想输出任意两点间的最短路径长度并不难,但如何输出之间经历过的顶点呢?在存入的时候首先存入p是离j最近的一点,然后是离p最近的一点,依次存入,最后一个存入的是离i最近的顶点,所以输出的时候必须要倒序输出。采用向前递归算法实现。在3x3循环里,如果A[i][j]=0或者A[i][j]=INF(INF是自定义的最大值)都是没有路径的,A[i][j]=0此时若i=j,则是顶点无路径。若A[i][j]=INF,则i到j没有路径。如果A[i][j]=0但是i!=j,此时说明从i到j得最短路径为0,路径是存在的,应该另外考虑这种情况,同下面的输出一致。若以上三种情况都不符合则执行正常的输出操作,输出从i到j经过的顶点,并输出路径长度。通过上述三个步骤,就解决了floyed算法的最短路径问题。
七、参考资料
[1] 王昆仑,李红. 数据结构与算法. 北京:中国铁道出版社,2006.5。
[2] 侯风巍,杨永田.数据结构要点精析.北京航空航天大学出版社,2006.8
八、运行截屏
1. 无向图floyed算法求最短路径如下:
1 |
2 |
4 |
3 |
10 |
3 |
4 |
7 |
图3无向图 图中双向箭头表示该两点不直接连接。权值用最大值9999表示
2有向图floyed算法求最点路径如下:
1 |
2 |
3 |
4 |
16 |
29 |
13 |
19 |
21 |
27 |
7 |
15 |
图4 有向图 图中没有标注的线条表示不连接,权值为9999
附录源代码如下:
//对于任意图,选择合适的数据结构存储,并实现求解最短路径的Floyd算法
#include<stdio.h>
#include<malloc.h>
#define ma 100
#define Null 0
#define INF 9999
typedef struct{
int data;//顶点信息
int num;//顶点序号
}vertex;
typedef struct{
int n;//顶点个数
int e;//边个数
vertex vexs[ma];//存储顶点
int edges[ma][ma];//存储边的权值
}mgraph;//邻接矩阵存储图
mgraph *create(){//创建临界矩阵存储图
int i,j,k,n,e,w; int c;
mgraph g,*mg=&g;
printf("初始化图,输入顶点数及边数");
scanf("%d%d",&n,&e);
mg->n=n;
mg->e=e;
printf("输入顶点信息,顶点序号\n");
for(i=1;i<=n;i++){
scanf("%d",&c);
mg->vexs[i].data=c;
mg->vexs[i].num=i;}
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
mg->edges[i][j]=0;
printf("输入边的信息,权值,相邻顶点有边,不连接权值为9999\n");
for(i=1;i<=e;i++)
{scanf("%d%d%d",&j,&k,&w);
mg->edges[j][k]=w;}
printf("得到邻接矩阵如下:\n");
for(i=1;i<=n;i++){
for(j=1;j<=n;j++)
printf("%d\t",mg->edges[i][j]);
printf("\n");}
return (mg);}
void ppath(int path[][ma], int i, int j)//向前递归查找路径上的顶点
{int k;
k=path[i][j];
if(k==-1) return;
ppath(path,i,k);
printf("%d->",k);
ppath(path,k,j);}
void dispath(int A[][ma],int path[][ma],int n)//输出所有顶点最短路径
{int i,j;
for(i=1; i<=n; i++)
for(j=1; j<=n; j++)
if(A[i][j]==INF){
if(i!=j) printf("从%d到%d没有路径\n",i,j);}
else if(A[i][j]==0){
if(i!=j)
{printf("从%d到%d的最短路径为:",i,j);
printf("%d->",i);
ppath(path,i,j);
printf("%d",j);
printf("\t路径长度为:%d\n",A[i][j]);}}
else{
printf("从%d到%d的最短路径为:",i,j);
printf("%d->",i);
ppath(path,i,j);
printf("%d",j);
printf("\t路径长度为:%d\n",A[i][j]);}}
void floyd(mgraph *g)//floyd算法处理矩阵
{int A[ma][ma], path[ma][ma];
int i, j, k;
for(i=1; i<=g->n; i++)
for(j=1; j<=g->n; j++){
A[i][j]=g->edges[i][j];
path[i][j]=-1;}
for(k=1; k<=g->n; k++){
for(i=1; i<=g->n; i++)
for(j=1; j<=g->n; j++)
if(A[i][k]!=0 && A[k][j]!=0&&A[i][j]>(A[i][k]+A[k][j])){
A[i][j]=A[i][k]+A[k][j];
path[i][j]=k;}}
printf("\n输出最短路径:");
i=g->n;
dispath(A,path,i);}
void main(){
mgraph *mg;
mg=create();
floyd(mg);}