项目描述:
在交通网络非常发达,交通工具和交通方式不断更新的今天,人们在出差、旅游或做其他出行时,不仅关心里程,也希望能节省交通费用。对于这样一个人们关心的问题,可用一个图结构来表示交通网络系统,并利用计算机建立一个交通咨询系统。图中顶点表示城市,边表示城市之间的交通关系。这个交通系统可以回答旅客提出的各种问题。例如,一位旅客要从A城到B城,他希望选择一条最低花费的路线。假设图中每一站都需要换车,那么这个问题反映到图上就是要找一条从顶点A到B所含边权值(花费)之和最小的路径(假设每条边的权值有2个,1个权值代表里程,1个权值代表花费)。
需求分析:
顾客想要从城市A到达城市B。,他想通过我们设计出的程序来得到最短的路程来安排自身的时间,或者想得到最少花费来估计自身预算,还有在得到较少的花费下得到可以去最多城市的路线图。本项目共分三部分,一是建立交通网络图的存储结构;二是解决单源最短路径问题;三是实现任两个城市顶点之间的最短路径问题。
逻辑结构定义:
该图中元素的逻辑结构可以定义为:G=(V,E)
其中E为数据元素的有限集合,v是E上关系的集合。
其中E={“北京”“西安”“天津”“郑州”“成都”“广州”“上海”}
V={<北京,西安>,<北京,天津>,<北京,郑州>,<北京,成都>,<北京,广州>,<北京,上海>,<西安,天津>,<西安,郑州>,<西安,成都>,<西安,广州>,<西安,上海>,<天津,郑州>,<天津,成都>,<天津,成都>,<天津,广州>,<天津,上海>,<郑州,成都>,<郑州,广州>,<郑州,上海>,<广州,上海>}
逻辑结构为非线性结构———图结构:
结构如下:
存储结构设计:
运用邻接矩阵来存储图:
图的邻接矩阵(Adjacency Matrix)存储方式是用两个数组来表示图。一个一维的数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
设图G有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
在图的术语中,我们提到了网的概念,也就是每条边上都带有权的图叫做网。那些这些权值就需要保存下来。设图G是网图,有n个顶点,则邻接矩阵是一个n*n的方阵,定义为:
如下图就是一个有向网图:
下面示例无向网图的创建代码:
#include<iostream>#define MAXVEX 10/*最大顶点数*/
#define INFINITY 65656/*表示权值无穷*/
using namespace std;
typedef int EdgeType;
typedef char VertexType;
typedef struct{
VertexType vexs[MAXVEX];
EdgeType arc[MAXVEX][MAXVEX];
int numNodes,numEdges;//图中当前顶点和边数
}MGraph; /*建立无向图的邻接矩阵表示*/
void CreateMGraph(MGraph *Gp)
{
int i,j,k,w;
cout<<"请输入顶点数和边数(空格分隔):"<<endl; cin>>Gp->numNodes>>Gp->numEdges;
cout<<"请输入顶点信息"<<endl;
for(i=0;i<Gp->numNodes;i++) //输入顶点
cin>> Gp->vexs[i];
for(i=0;i<Gp->numNodes;i++) //初始化邻接矩阵
{
for(j=0;j<Gp->numNodes;j++)
{
if(i==j)
Gp->arc[i][j]=0;//顶点没有到自己的边 else
Gp->arc[i][j]=INFINITY;//初始化边为无穷
}
} /*输入边的上下标和权值*/
for(k=0;k<Gp->numEdges;k++)
{
cout<<"请输入"<<Gp->numEdges<<"个边(Vi,Vj)的上标、下标和权值w(空格分隔)"<<endl;
cin>>i>>j>>w;
Gp->arc[i][j]=w;
Gp->arc[j][i]=Gp->arc[i][j];//对称矩阵
}
}
int main(void)
{
MGraph MG;
CreateMGraph(&MG);
return 0;}
设计思路:
(1)数据存储。城市信息、交通信息(城市间的里程、旅费和时间)存储于磁盘文件。建议把城市信息、交通信息还有城市和交通信息数目分开存于三个文件中。
(2)数据的逻辑结构。根据设计任务的描述,其城市之间的旅游交通问题是典型的图结构,可看作为有向图,图的顶点是城市,边是城市之间所耗费的旅费、里程。
(3)数据的存储结构。采用邻接矩阵作为数据的存储结构,提高空间的存储效率。
(4)用不同的功能模块对城市信息和交通信息进行编辑,可用菜单方式或命令提示方式。只要能方便的对城市信息和交通信息进行管理即可。
(6)主程序可以有系统界面、菜单;也可用命令提示方式;选择功能模块执行, 要求在程序运行过程中可以反复操作。
图的建立:
- 算法:构造函数CreateMGraph
- 输入:顶点的数据信息指针G,顶点个数n,边的个数e,边上的权值(路程或花费)。
- 输出:<1>存储图的顶点个数和边数 <2>将顶点信息储存到一维数组vexs中 <3>初始化邻接矩阵edge <4>依次输入每条边依附的两个顶点编号i和j.
迪杰斯特拉算法:
假设路网中每一个节点都有标号(dt,pr),dt是从出发点s到点t的最短路径长度;pt表示从s到t的最短路径中t点的前一个点。求解从出发点s到点t的最短路径算法的基本过程为:
<1>初始化。出发点设置为ds=0;ps等于空;所有其他点:di=∞,pi未定义;标记起源点s,记k = s,其他所有点设为未标记。
<2>检验从所有已标记的点k到其他直接连接的未标记的点j的距离,并设置:dj=min[dj,dk+w(k,j)];
其中,w(k,j)表示从k到j的路径长度。
<3> 选取下一个点。从所有未标记的点中选取 最小的点i,点i被选为最短路径中的一点,并设为已标记的。
<4> 找到点i的前一点。从已经标记的点集合中找到直接连接到点i的点,并标记为 1。
<5>标记点i。如果所有的点已标记,则算法结束。否则,记k = i,转到2继续。
从以上算法的步骤中可以看出 :dijkstra算法的关键部分是从未标记的点中不断地找出距离源点距离最近的点,并把改点加入到标记的点集合中,同时更新未标记的点集合中其余点到起始点的最短估计距离[z1]
以一个带有权值的无向图为例,用dijkstra算法分析从源点A到目标点F的最短路径。
(1)用带有权值的一个矩阵w表示含有n各节点的带权无向图, 代表弧段 的权值,如果从节点 到节点 不连通,那么 ,带权值图邻接矩阵如下图所示.设置A为源点,G为目的点, 代表从节点A到有向图中其他节点 的最短路径长度。设置初始值di=<A,vi>,vi∈{A,B,C,D,E,F},k代表标记的节点集合。
(2)选择|vj,使d(j)=min{d(i)|vi∈{B,C,D,E,F}},vj是从A点出发求出的一条最短路径上的终止节点,令K=K∪{vj};
(3) 修改起始节点A到集合{B,C,D,E,F}中任一节点Vk之间的最短路径的长度值,如果d(j)+w(j,k) < d(k),那么d(k) = d(j) + w(j,k);
(4)重复步骤2、3的操作N-1次,最终得到从起始节点A到其他节点的最短路径,按照递增的顺序排列路径的长度。
迪杰斯特拉的流程图如下:
3.弗洛伊德算法:
- Floyd算法的基本思想:
可以将问题分解:
第一、先找出最短的距离
第二、然后在考虑如何找出对应的行进路线。如何找出最短路径呢,这里还是用到动态规划的知识,对于任何一个城市而言,i到j的最短距离不外乎存在经过i与j之间经过k和不经过k两种可能,所以可以令k=1,2,3,...,n(n是城市的数目),在检查d(ij)与d(ik)+d(kj)的值;在此d(ik)与d(kj)分别是目前为止所知道的i到k与k到j的最短距离,因此d(ik)+d(kj)就是i到j经过k的最短距离。所以,若有d(ij)>d(ik)+d(kj),就表示从i出发经过k再到j的距离要比原来的i到j距离短,自然把i到j的d(ij)重写为d(ik)+d(kj),每当一个k查完了,d(ij)就是目前的i到j的最短距离。重复这一过程,最后当查完所有的k时,d(ij)里面存放的就是i到j之间的最短距离了。
Floyd算法的基本步骤:
- 定义n×n的方阵序列D-1, D0 , … Dn-1,
- 初始化: D-1=C
- D-1[i][j]=边<i,j>的长度,表示初始的从i到j的最短路径长度,即它是从i到j的中间不经过其他中间点的最短路径。
- 迭代:设Dk-1已求出,如何得到Dk(0≤k≤n-1)?
- Dk-1[i][j]表示从i到j的中间点不大于k-1的最短路径p:i…j,
- 考虑将顶点k加入路径p得到顶点序列q:i…k…j,
- 若q不是路径,则当前的最短路径仍是上一步结果:Dk[i][j]= Dk-1[i][j];
- 否则若q的长度小于p的长度,则用q取代p作为从i到j的最短路径
- 因为q的两条子路径i…k和k…j皆是中间点不大于k-1的最短路径,所以从i到j中间点不大于k的最短路径长度为:
Dk[i][j]=min{ Dk-1[i][j], Dk-1[i][k] +Dk-1[k][j] }
Floyd算法实现:
- 可以用三个for循环把问题搞定了,但是有一个问题需要注意,那就是for循环的嵌套的顺序:我们可能随手就会写出这样的程序,但是仔细考虑的话,会发现是有问题的。
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
for(int k=0; k<n; k++) - 问题出在我们太早的把i-k-j的距离确定下来了,假设一旦找到了i-p-j最短的距离后,i到j就相当处理完了,以后不会在改变了,一旦以后有使i到j的更短的距离时也不能再去更新了,所以结果一定是不对的。所以应当象下面一样来写程序:
for(int k=0; k<n; k++)
for(int i=0; i<n; i++)
for(int j=0; j<n; j++) - 这样做的意义在于固定了k,把所有i到j而经过k的距离找出来,然后象开头所提到的那样进行比较和重写,因为k是在最外层的,所以会把所有的i到j都处理完后,才会移动到下一个k,这样就不会有问题了,看来多层循环的时候,我们一定要当心,否则很容易就弄错了。
路径查找:
接下来就要看一看如何找出最短路径所行经的城市了,这里要用到另一个矩阵P,它的定义是这样的:p(ij)的值如果为p,就表示i到j的最短行经为i->...->p->j,也就是说p是i到j的最短行径中的j之前的最后一个城市。P矩阵的初值为p(ij)=i。有了这个矩阵之后,要找最短路径就轻而易举了。对于i到j而言找出p(ij),令为p,就知道了路径i->...->p->j;再去找p(ip),如果值为q,i到p的最短路径为i->...->q->p;再去找p(iq),如果值为r,i到q的最短路径为i->...->r->q;所以一再反复,到了某个p(it)的值为i时,就表示i到t的最短路径为i->t,就会的到答案了,i到j的最短行径为i->t->...->q->p->j。因为上述的算法是从终点到起点的顺序找出来的,所以输出的时候要把它倒过来。
- 但是,如何动态的回填P矩阵的值呢?回想一下,当d(ij)>d(ik)+d(kj)时,就要让i到j的最短路径改为走i->...->k->...->j这一条路,但是d(kj)的值是已知的,换句话说,就是k->...->j这条路是已知的,所以k->...->j这条路上j的上一个城市(即p(kj))也是已知的,当然,因为要改走i->...->k->...->j这一条路,j的上一个城市正好是p(kj)。所以一旦发现d(ij)>d(ik)+d(kj),就把p(kj)存入p(ij)。
迪杰斯特拉算法:
void Dijkstra(MGraph *G,int v1,int n)
{
int D2[MVNum],P2[MVNum];
int v,i,w,min;
enum boolean S[MVNum];
for(v=1;v<=n;v++)
{
S[v]=FALSE;
D2[v]=G->arcs[v1][v];
if(D2[v]<Maxint)
P2[v]=v1;
else
P2[v]=0;
}
D2[v1]=0;S[v1]=TRUE;
for(i=2;i<n;i++)
{
min=Maxint;
for(w=1;w<=n;w++)
if(!S[w]&&D2[w]<min)
{
v=w;min=D2[w];
}
S[v]=TRUE;
for(w=1;w<=n;w++)
if(!S[w]&&(D2[v]+G->arcs[v][w]<D2[w]))
{
D2[w]=D2[v]+G->arcs[v][w];
P2[w]=v;
}
}
printf("路径长度 路径\n");
for(i=1;i<=n;i++)
{
printf("%5d",D2[i]);
printf("%5d",i);v=P2[i];
while(v!=0)
{
printf("<-%d",v);
v=P2[v];
}
printf("\n");
}
}
费洛伊德算法:
void Floyd(MGraph *G,int n)
{ int i,j,k,v,w;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{ if(G->arcs[i][j]!=Maxint)
P[i][j]=j;
else
P[i][j]=0;
D[i][j]=G->arcs[i][j];
}
for(k=1;k<=n;k++)
{
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
if(D[i][k]+D[k][j]<D[i][j])
{
D[i][j]=D[i][k]+D[k][j];
P[i][j]=P[i][k];;
}
}
}
}
建立有向图的存储结构 :
void CreateMGraph(MGraph * G,int n,int e)
{
int i,j,k,w;
for(i=1;i<=n;i++)
G->vexs[i]=(char)i;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
G->arcs[i][j]=Maxint;
printf("输入%d条边的i,j及w:\n",e);
for(k=1;k<=e;k++)
{
scanf("%d,%d,%d",&i,&j,&w);
G->arcs[i][j]=w;
}
printf("有向图建立完毕\n");
}
主函数:
void main()
{ MGraph *G;
int m,n,e,v,w,k;
int xz=1;
G=(MGraph *)malloc(sizeof(MGraph));
printf("输入图中顶点个数和边数n,e:");
scanf("%d,%d",&n,&e);
CreateMGraph(G,n,e);
while (xz!=0)
{ printf("******求城市间的最短路径******\n");
printf("1.求一个城市到所有城市的最短路径\n");
printf(“2.求任意的两个城市之间的最短路径\n");
printf(" 请选择:1 或 2,选择 0 退出:");
scanf("%d",&xz);
if(xz==2)
{
Floyd(G,n);
printf("输入起点和终点:v,w:");
scanf("%d,%d",&v,&w);
k=P[v][w];
if(k==0)
printf("顶点 %d 到 %d 无路径!\n",v,w);
else
{ printf("从顶点%d到%d的最短路径是::%d",v,w,v);
while(k!=w)
{
printf("→¨²%d",k);
k=P[k][w];
}
printf("→¨²%d",w);
printf("路径长度:%d\n",D[v][w]);
}
}
else if(xz==1)
{
printf("求单源路径,输入源点 v :");
scanf("%d",&v);
Dijkstra(G,v,n);
}
}
printf("结束求最短路径");
}
最终运行代码:
#include<stdio.h>
#include<stdlib.h>
#pragma warning(disable:4996)
#define MVNum 100 //最大顶点数
#define Maxint 35000
enum boolean{FALSE,TRUE};
typedef char Vertextype;
typedef int Adjmatrix;
typedef struct
{
Vertextype vexs[MVNum]; //顶点数组,类型假定为char型
Adjmatrix arcs[MVNum] [MVNum]; // 邻接矩阵,假定为int型
} MGraph;
int D1[MVNum], p1[MVNum];
int D[MVNum][MVNum],p[MVNum][MVNum];
//文件名save.c
void CreateMGraph(MGraph *G,int n,int e)
{ //采用邻接矩阵表示法构造有向图G,n,e表示图的当前顶点数和边数
int i,j,k,w;
for(i=1;i<=n;i++) //输入顶点信息
G->vexs[i]=(char)i;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
G->arcs[i][j]=Maxint; // 初始化邻接矩阵
printf ("输入%d条边的i,j及w: \n",e);
for(k=1;k<=e;k++)
{ //读入e条边,建立邻接矩阵
scanf("%d,%d,%d",&i,&j,&w);
G->arcs[i][j]=w;
}
printf ("有向图的存储结构建立完毕!\n");
}
//文件名:dijkstra.c(迪杰斯特拉算法)
void Dijkstra(MGraph *G, int v1,int n)
{ //用Dijkstra算法求有向图G的v1顶点到其他顶点v的最短路径p[v]及其权D[v]
//设G是有向图的邻接矩阵,若边<i,j>不存在,则G[i][j]=Maxint
//S[v]为真当且仅当v属于S,及以求的从v1到v的最短路径
int D2[MVNum], p2[MVNum];
int v,i,w,min;
enum boolean S[MVNum];
for(v=1;v<=n;v++)
{ // 初始化S和D
S[v]=FALSE; //置空最短路径终点集
D2[v]=G->arcs[v1][v]; //置初始的最短路径值
if(D2[v]< Maxint)
p2[v]=v1; //v1是的前趋(双亲)
else
p2[v]=0; //v 无前趋
} // End_for
D2[v1]=0;S[v1]=TRUE; //S集初始时只有源点,源点到源点的距离为
//开始循环,每次求的V1到某个V顶点的最短路径,并加V到S集中
for(i=2;i<n;i++)
{ //其余n-1个顶点
min=Maxint; // 当前所知离v1顶点的最近距离,设初值为∞
for(w=1;w<=n;w++) //对所有顶点检查
if(!S[w] && D2[w]<min)
{ //找离v1最近的顶点w,并将其赋给v,距离赋给min
v=w; //在S集之外的离v1最近的顶点序号
min=D2[w]; //最近的距离
} //W顶点距离V1顶点更近
S[v]=TRUE; //将v并入S集
for(w=1;w<=n;w++) //更新当前最短路径及距离
if(!S[w]&&(D2[v]+G->arcs[v][w]<D2[w]))
{ //修改D2[w]和p2[w],w 属于V-S
D2[w]=D2[v]+G->arcs[v][w]; //更新D2[w]
p2[w]=v;
} //End_if
} //End_for
printf ("路径长度路径\n");
for(i=1;i<=n;i++)
{ printf ("%5d", D2[i]);
printf ("%5d", i);v=p2[i];
while(v!=0) {
printf ("<-%d", v);
v=p2[v];
}
printf("\n");
}
}
//文件名floyd.c(费洛伊德算法)
void Floyd(MGraph *G, int n)
{
int i, j, k;
for(i=1;i<=n;i++) //设置路径长度D和路径path初值
for(j=1;j<=n;j++)
{
if(G->arcs[i][j]!=Maxint)
p[i][j]=j; //j是i的后继
else
p[i][j]=0;
D[i][j]=G->arcs[i][j];
}
for(k=1;k<=n;k++) {
{ //做K次迭代,每次均试图将顶点K扩充到当前求得的从i到j的最短路径pij上
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{ if(D[i][k]+D[k][j]<D[i][j]) {
D[i][j]=D[i][k]+D[k][j]; //修改长度
p[i][j]=p[i][k];
}
}
}
}
}
void main()
{ MGraph * G;
int n, e, v, w, k;
int xz=1;
G=(MGraph *)malloc(sizeof(MGraph));
printf("输入图中顶点个数和边数n,e: ");
scanf("%d,%d", &n, &e);
CreateMGraph(G, n, e); //建立图的存储结构
while(xz!=0)
{
printf(" 求城市之间的最断路径 \n");
printf("-------------------------------\n");
printf("1.求一个城市到所有城市的最短路径\n");
printf("2.求任意的两个城市之间的最短路径\n");
printf("--------------------------------\n");
printf(" 请选择:或2,选择:退出:");
scanf("%d",&xz);
if(xz==2)
{
Floyd(G,n); //调用费洛伊德算法
printf("输入起点和终点: v,w:");
scanf("%d,%d",&v,&w );
k=p[v][w]; //k为起点v的后继顶点
if(k==0)
printf("顶点%d 到 %d 无路径! \n",v,w);
else
{
printf("从顶点%d到%d的最短路径是: %d",v,w,v);
}
while(k!=w)
{
printf("-->%d",k); //输出后继顶点
k=p[k][w]; //继续找下一个后继顶点
}
printf("-->%d",w); // 输出终点w
printf(" 路径长度:%d\n",D[v][w]);
}
if(xz==1)
{
printf("求单源路径,输入起点 v :");
scanf("%d", &v);
Dijkstra(G,v,n); //调用迪杰斯特拉算法
}
}
printf("结束求最短路径,再见!\n");
}