交通咨询系统设计
一、问题描述
在交通网络非常发达,交通工具和交通方式不断更新的今天,人们在出差、旅游或做其他出行时,不仅关心节省交通费用,而且对里程和所需要的时间等问题也感兴趣。对于这样一个人们关心的问题,可用一个图结构来表示交通网络系统,利用计算机建立一个交通咨询系统。图中的顶点表示城市,边表示城市之间的交通关系。这个交通系统可以回答出行旅客提出的各种路径选择问题。例如,问题之一:“一位旅客要从A城到B城,他希望选择一条途中中转次数最少的路线。”假设图中每一站都需要换车,那么这个问题反映到图上就是要找一条从顶点A到顶点B的所含边数目最少的路径。我们只需要从顶点A出发对图作广度优先搜索,一旦遇到顶点B就终止。由此所得广度优先生成树上,从根顶点A到顶点B的路径就是中转次数最少的路径。路径上A与B之间的顶点就是路径的中转站,但这只是一类最简单的图的最短路径问题。系统还可以回答诸如此类的等等的路径选择问题。
设计一个交通咨询系统,为出差、旅游或做其他出行的客人提供各种路径选择信息查询服务。
二、需求分析
设计一个交通咨询系统,能让旅客咨询从任一个城市顶点到另一城市顶点之间的最短路径(里程)或最低花费或最少时间等问题。对于不同的咨询要求,可输入城市间的路程或所需时间或所需费用。
本设计共分三部分,一是建立交通网络图的存储结构;二是解决单源最短路径问题;三是实现任两个城市顶点之间的最短路径问题。
2.1建立图的存储结构
邻接矩阵是表示图形中顶点之间相邻关系的矩阵。图的邻接矩阵是定义如下的n阶方阵:
设G=(V,E)是一个图,结点集为
。
G的邻接矩阵
当邻接矩阵的行表头、列表头顺序一定时,一个图的邻接矩阵表示是唯一的。
图的邻接矩阵表示,除了需用一个二维数组存储顶点之间的相邻关系的邻接矩阵外,通常还需要使用一个具有n个元素的一维数组来存储顶点信息,其中下标为i的元素存储顶点i的信息。因此,图的邻接矩阵的存储结构定义如下:
#definf MVNum 50 //最大顶点数
typedef struct
{
VertexType vexs[MVNum]; //顶点数组,类型假定为char型
Adjmatrix arcs[MVNum][MVNum]; //邻接矩阵,假定为int型
}MGraph;
2.2单源最短路径(迪杰斯特拉算法)
最短路径的提法很多。在这里先讨论单源最短路径问题:即已知有向图(带权),我们希望找出从某个源点S V到G中其余各顶点的最短路径。
为了叙述方便,我们把路径上的开始点称为源点,路径的最后一个顶点为终点。
那么,如何求得给定有向图的单源最短路径呢?迪杰斯特拉(Dijkstra)提出按路径长度递增产生诸点的最短路径算法,称之为迪杰斯特拉算法。
迪杰斯特拉算法求最短路径的实现思想是:设G=(V,E)是一个有向图,结点集为, ,cost是表示G的邻接矩阵,cost[i][j]表示有向边<i,j>的权。若不存在有向边<i,j>,则cost[i][j]的权为无穷大(这里取值为32767)。设S是一个集合,其中的每个元素表示一个顶点,从源点到这些顶点的最短距离已经求出。设顶点v1为源点,集合S的初态只包含一个元素,即顶点v1。数组dist记录从源点到其他顶点当前的最短距离,其初值为dist[i]=cost[v1][i],i=1,2,……,n。从S之外的顶点集合V-S中选出一个顶点w,使dist[w]的值最小。于是从源点到达w只通过S中顶点,把w加入集合S中,调整dist中记录的从源点到V-S中每个顶点v的距离:从原来的dist[v]和dist[w]+cost[w][v]中选择较小的值作为新的dist[v]。重复上述过程,直到V-S为空。
最终结果是:S记录了从源点到该顶点存在最短路径的顶点集合,数组dist记录了源点到V中其余各顶点之间的最短路径,path是最短路径的路径数组,其中path[i]表示从源点到顶点i之间的最短路径的前驱顶点。
因此,迪杰斯特拉算法可用自然语言描述如下:
初始化S和D,置空最短路径终点集,置初始的最短路径值;
S[v1]=TRUE; D[v1]=0; //S集初始时只有源点,源点到源点的距离为0;
While (S集中顶点数<n)
{
开始循环,每次求得v1到某个v顶点的最短路径,并加v到S集中;
S[v]=TRUE;
更新当前最短路径及距离;
}
2.3任意一对顶点间最短路径(费洛伊德算法)
任意一对顶点间最短路径问题,是对于给定的有向网络图G=(V,E),要对G中任意一对顶点有序对“v,w(v 不等于w)”,找出v到w的最短路径。
要解决这个问题,我们可以依次把有向网络图中每个顶点作为源点,重复执行前面讨论的迪杰斯特拉算法n次,即可以求得每对顶点之间的最短路径。
这里还可以用另外一种方法,称作费洛伊德(Floyd)算法。
费洛伊德(Floyd)算法算法的基本思想是:假设求从顶点 vi到vj的最短路径。如果从vi到vj存在一条长度为arcs[i][j]的路径,该路径不一定是最短路径,还需要进行n次试探。首先考虑路径<vi,v1>和<v1,vj>是否存在。如果存在,则比较<vi,vj>和< vi,v1,vj >的路径长度,取长度较短者为当前所求得的最短路径。该路径是中间顶点序号不大于1的最短路径。其次,考虑从vi到vj是否包含有顶点v2为中间顶点的路径<vi,…,v2,…,vj>,若没有,则说明从vi到vj的当前最短路径就是前一步求出的;若有,那么<vi,…,v2,…,vj>可分解为<vi,…v2>和<v2,…,vj>,而这两条路径是前一次找到的中间顶点序号不大于1的最短路径,将这两条路径长度相加就得到路径<vi,…,v2,…,vj>的长度。将该长度与前一次中求出的从vi到vj的中间顶点序号不大于1的最短路径比较,取其长度较短者作为当前求得的从vi到vj的中间顶点序号不大于2的最短路径。依此类推,直到顶点vn加入当前从vi到vj的最短路径后,选出从vi到vj的中间顶点序号不大于n的最短路径为止。由于图G中顶点序号不大于n,所以vi到vj的中间顶点序号不大于n的最短路径,已考虑了所有顶点作为中间顶点的可能性,因此,它就是vi到vj的最短路径。
三、概要设计
1.设定邻接矩阵的抽象数据类型为:
ADT VertexType{
数据对象:D={ai,j|ai,j∈{},0≤i≤m+1,0≤j≤n+1,m,n≤10}
数据关系:R={ROW,COL}
ROW={<ai-1,j,ai,j>|ai-1,j,ai,j∈D,i=1,……,m+1,j=0,……,n+1}
COL={<ai,j-1,ai,j>|ai,j-1,ai,j∈D,i=0,……,m+1,j=1,……,n+1}
基本操作:
typedef struct {
VertexType vexs[Vertex_NUM];
int arcs[Vertex_NUM][Vertex_NUM];
int vn, en;
} MGraph;
操作结果:建立一个图的邻接矩阵
}ADT VertexType
2.设定图的抽象数据类型定义为:
ADT Graph{
数据对象:D={ai|ai∈charset,i=1,2,……,n,n≥0}
数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2……,n}
基本操作:
CreateMGraph()
操作结果:建立图的储存结构:有向图、无向图
void Floyd(G)
初始条件:建立一个图结构
操作结果:多源最短路,输出邻接矩阵
void Dijkstra(G)
初始条件:建立一个图结构
操作结果:所求结点到其他结点的最短路
void Inquiry( G)
初始条件:建立一个图结构
操作结果:查询图中两个节点的最短路径并输出
}ADT Graph
- 本程序包含三个模块
1)主程序模块
void main( )
{ 初始化
do{
接受命令;
处理命令;
}while(命令!=“退出”);
}
2)图模块----实现图抽象数据类型
3)邻接矩阵模块----实现邻接矩阵抽象数据类型
各模块之间的调用关系如下:
- 迪杰斯特拉的伪码算法:
1:找出未标记过的离起点最近的点(如果没有的话,就中止算法)
2:以该点为中心点更新该点周围的点
3:反复执行
void Graph::Dijkstra(int vertex)
{
//注意:下标表示结点
int count = 0; //用于记录访问过的结点数目,后面用于控制循环
bool find[max_size]; //用于标记已经找到最短路径的结点
int pre[max_size]; //用于存放当前结点的前驱结点的最短路径
int distance[max_size]; //用于存放当前结点的最短路径
//初始化在这里插入代码片
for(int i=0;i<max_size;i++)
pre[i] = vertex; //开始所有结点的前驱结点都是开始的vertex
for(int i=0;i<max_size;i++)
distance[i] = adjacent[vertex][i]; //邻接矩阵中存放的权值就是距离
for(int i=0;i<max_size;i++)
find[i] = false; //初始化所有结点都没有找到最短路径
ind[vertex] = true;
int v = vertex; //用来迭代顶点的变量
int d; //用来表示距离
while(count < max_size) //count用于记录访问过的次数
{
d = infinity;//开始置为无穷
for(int i=0;i<max_size;i++) //找到离最初结点最短路径的一个未访问到的结点
{
if(!find[i] && distance[i]<d) //
{
d = diatance[i];
v = i;
}
}
find[v] = true;//表示节点放到S中
//更新剩余的结点的前驱和最短距离
for(int i=0;i<max_size;i++)
{
if(!find[i])
{
/*将上面找到的最短路径的结点作为起始点,
*连到其他未访问过的结点上,
*当比从最初结点到这个结点的路径短的时候,
*就将上个结点作为前驱结点,更新一下即可*/
d = distance[v] + adjacent[v][i];
if(d < distance[i])
{
pre[i] = v;
distance[i] = d;
}
}
}
count++;
}
}
4.弗洛伊德的伪码算法:
// dist(i,j) 为从节点i到节点j的最短距离
For i←1 to n do
For j←1 to n do
dist(i,j) = weight(i,j)
For k←1 to n do // k为“媒介节点”
For i←1 to n do
For j←1 to n do
if (dist(i,k) + dist(k,j) < dist(i,j)) then // 是否是更短的路径?
dist(i,j) = dist(i,k) + dist(k,j)
1.邻接矩阵类型
typedef struct {
int no; //顶点编号
} VertexType; //顶点类型
2.图类型
typedef struct {
VertexType vexs[Vertex_NUM]; //存放顶点信息
int arcs[Vertex_NUM][Vertex_NUM]; //邻接矩阵的边数组
int vn, en; //顶点数,边数
} MGraph; //完整的图邻接矩阵类型
void Floyd(G)
//多源最短路,输出邻接矩阵
void Dijkstra(G)
//所求结点到其他结点的最短路
void Inquiry( G)
//查询图中两个节点的最短路径并输出
MGraph* CreateMGraph()
//建立图的储存结构
其中部分操作的算法:
MGraph* CreateMGraph()
{
int i, j, w;
MGraph* G;
int n, m;//顶点个数 边的个数
G = (MGraph*)malloc(sizeof(MGraph)); //建立图的存储结构
G->vn = n;
G->en = m;
for (i = 1; i <= G->vn; i++) {
G->vexs[i].no = i;
}
for (i = 1; i <= G->vn; i++)
for (j = 1; j <= G->vn; j++)
G->arcs[i][j] = INF;
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
G->arcs[i][j] = (i == j) ? 0 : INF;
for (i = 1; i <= m; i++) {
int t1, t2, d;//位置t1到位置t2的距离是d
cin >> t1 >> t2 >> d;
// G->arcs[t1][t2]=G->ar[t2][t1]=d;//无向图
G->arcs[t1][t2] = d;//-----------------有向图
}
printf("\n图的存储建立完毕!\n");
return G;
}
void Inquiry(MGraph* G)
{
int i;
string t;
string t1;
cout << "请输入你要查询的城市名:";
cin >> t >> t1;
for (i = 1; i <= G->vn; i++) {
if (citys[i] == t)
break;
}
if (i == G->vn + 1) {
cout << endl << "所查城市不在交通网络图中" << endl;
getchar();
return;
}
cout << endl << "所求城市" << citys[i] << "单源最短路径为:" << endl;
for (int j = 1; j <= G->vn; j++) {
if (i == j) continue;
if (citys[j] == t1)
{
cout << citys[i] << "->" << citys[j] << ": " << G->arcs[i][j] << endl;
}
}
}
3.求最短路径的算法:
void Floyd(MGraph* G)//多源最短路
{
for (int i = 1; i <= G->vn; i++)
for (int j = 1; j <= G->vn; j++)
for (int k = 1; k <= G->vn; k++)
if (G->arcs[i][k] + G->arcs[k][j] < G->arcs[i][j])
G->arcs[i][j] = G->arcs[i][k] + G->arcs[k][j];
}// Floyd
void Dijkstra(MGraph* G)//贪心思想
{
int dis[10], book[10]; //所求结点到其他结点的最短路 记录当前结点是否已经判断过了
//初始化单源最短数组dis,这里是1号顶点到其余各顶点的初始路径
for (int i = 1; i <= G->vn; i++)
dis[i] = G->arcs[1][i];
for (int i = 2; i <= G->vn; i++)
book[i] = 0;//未知最短路顶点
book[1] = 1;
//Dijkstra 算法核心语句
for (int i = 1; i <= G->vn - 1; i++) {
int min = INF, t;
for (int j = 1; j <= G->vn; j++)
if (book[j] == 0 && dis[j] < min) { //找出未判断结点中到1号结点(单源最短路起点)距离最小结点
min = dis[j];
t = j;
}
book[t] = 1;//及时将选出的该结点标记为已判断过了
for (int v = 1; v <= G->vn; v++) { //判断其他结点借助当前选出的结点,是否使得到1号结点的距离变短
if (G->arcs[t][v] < INF) {
if (dis[v] > dis[t] + G->arcs[t][v])
dis[v] = dis[t] + G->arcs[t][v];//记录借助当前选出结点使得到1号结点更短路径的新距离
}
}
}//Dijkstra
4.主函数和其他函数的伪码算法
void main( )
{//主程序
Initialization(); //初始化
do{
ReadCommand(cmd);//读入一个操作命令符
Interpret(cmd); //解释执行操作命令符
}while(输入信息);
}//main
void Initialization()
{ //系统初始化
MGraph* G;
G = CreateMGraph();
}//Initialization
void ReadCommand(char &cmd)
{ //读入操作命令符
显示键入操作命令符的提示信息;
do{
Floyd(G);
Dijkstra(G);
}while(输入信息);
}//ReadCommand
void Interpret(char cmd)
{
Inquiry(G);
return 0;
}//InterPret
5.函数的调用关系图反映了演示程序的层次结构
typedef struct {
int no; //顶点编号
} VertexType; //顶点类型
typedef struct {
VertexType vexs[Vertex_NUM]; //存放顶点信息
int arcs[Vertex_NUM][Vertex_NUM]; //邻接矩阵的边数组
int vn, en; //顶点数,边数
} MGraph; //完整的图邻接矩阵类型
2. 迪杰斯特拉算法
void Dijkstra(MGraph*G)//贪心思想
{
int dis[10],book[10]; //所求结点到其他结点的最短路 记录当前结点是否已经判断过了
//初始化单源最短数组dis,这里是1号顶点到其余各顶点的初始路径
for(int i=1; i<=G->vn; i++)
dis[i]=G->arcs[1][i];
for(int i=2; i<=G->vn; i++)
book[i]=0;//未知最短路顶点
book[1]=1;
//Dijkstra 算法核心语句
for(int i=1; i<=G->vn-1; i++) {
int min=INF,t;
for(int j=1; j<=G->vn; j++)
if(book[j]==0&&dis[j]<min) { //找出未判断结点中到1号结点(单源最短路起点)距离最小结点
min=dis[j];
t=j;
}
book[t]=1;//及时将选出的该结点标记为已判断过了
for(int v=1; v<=G->vn; v++) { //判断其他结点借助当前选出的结点,是否使得到1号结点的距离变短
if(G->arcs[t][v]<INF) {
if(dis[v]>dis[t]+G->arcs[t][v])
dis[v]=dis[t]+G->arcs[t][v];//记录借助当前选出结点使得到1号结点更短路径的新距离
}
}
}
cout<<"所求城市单源最短路径为:"<<endl;
for(int i=2; i<=G->vn; i++)
if(dis[i]<INF)//如果大于等于说明不存在该路径
cout<<citys[1]<<"->"<<citys[i]<<": "<<dis[i]<<endl;
3. 费洛伊德算法
void Floyd(MGraph*G)//多源最短路
{
for(int i=1; i<=G->vn; i++)
for(int j=1; j<=G->vn; j++)
for(int k=1; k<=G->vn; k++)
if(G->arcs[i][k]+G->arcs[k][j]<G->arcs[i][j])
G->arcs[i][j]=G->arcs[i][k]+G->arcs[k][j];
for(int i=1; i<=G->vn; i++)
cout<<"\t"<<citys[i];
cout<<endl;
for(int i=1; i<=G->vn; i++) {
cout<<citys[i];
//-----------无向图--------------
for(int j=1; j<=G->vn; j++)
if(G->arcs[i][j]==INF){
printf("\t");
}else{
printf("%8d",G->arcs[i][j]);
}
cout<<endl;
}
}}
4. 运行主控程序
int main()
{
MGraph *G;
G = CreateMGraph();
Floyd(G);
Dijkstra(G);
Inquiry(G);
system("pause");
return 0;
}
五、设计功能的实现(用C或C++语言描述)
#include <bits/stdc++.h>
using namespace std;
#define Vertex_NUM 50 // 最大顶点数
#define INF 32767 // 无穷大∞
string citys[10]={"0","a","b","c","d","e","f","g"};//-----------------有向图
//邻接矩阵的类型定义如下:
typedef struct {
int no; //顶点编号
} VertexType; //顶点类型
typedef struct {
VertexType vexs[Vertex_NUM]; //存放顶点信息
int arcs[Vertex_NUM][Vertex_NUM]; //邻接矩阵的边数组
int vn, en; //顶点数,边数
} MGraph; //完整的图邻接矩阵类型
void Floyd(MGraph*G)//多源最短路
{
for(int i=1; i<=G->vn; i++)
for(int j=1; j<=G->vn; j++)
for(int k=1; k<=G->vn; k++)
if(G->arcs[i][k]+G->arcs[k][j]<G->arcs[i][j])
G->arcs[i][j]=G->arcs[i][k]+G->arcs[k][j];
for(int i=1; i<=G->vn; i++)
cout<<"\t"<<citys[i];
cout<<endl;
for(int i=1; i<=G->vn; i++) {
cout<<citys[i];
//-----------有向图--------------
for(int j=1; j<=G->vn; j++)
if(G->arcs[i][j]==INF){
printf("\t");
}else{
printf("%8d",G->arcs[i][j]);
}
cout<<endl;
}
}
void Dijkstra(MGraph*G)//贪心思想
{
int dis[10],book[10]; //所求结点到其他结点的最短路 记录当前结点是否已经判断过了
//初始化单源最短数组dis,这里是1号顶点到其余各顶点的初始路径
for(int i=1; i<=G->vn; i++)
dis[i]=G->arcs[1][i];
for(int i=2; i<=G->vn; i++)
book[i]=0;//未知最短路顶点
book[1]=1;
//Dijkstra 算法核心语句
for(int i=1; i<=G->vn-1; i++) {
int min=INF,t;
for(int j=1; j<=G->vn; j++)
if(book[j]==0&&dis[j]<min) { //找出未判断结点中到1号结点(单源最短路起点)距离最小结点
min=dis[j];
t=j;
}
book[t]=1;//及时将选出的该结点标记为已判断过了
for(int v=1; v<=G->vn; v++) { //判断其他结点借助当前选出的结点,是否使得到1号结点的距离变短
if(G->arcs[t][v]<INF) {
if(dis[v]>dis[t]+G->arcs[t][v])
dis[v]=dis[t]+G->arcs[t][v];//记录借助当前选出结点使得到1号结点更短路径的新距离
}
}
}
cout<<"所求城市单源最短路径为:"<<endl;
for(int i=2; i<=G->vn; i++)
if(dis[i]<INF)
cout<<citys[1]<<"->"<<citys[i]<<": "<<dis[i]<<endl;
}
void Inquiry(MGraph*G )
{
int i;
string t;
string t1;
cout<<"请输入你要查询的城市名:";
cin>>t>>t1;
for(i=1; i<= G->vn; i++) {
if(citys[i]==t)
break;
}
if(i== G->vn+1) {
cout<<endl<<"所查城市不在交通网络图中"<<endl;
getchar();
return ;
}
cout<<endl<<"所求城市"<<citys[i]<<"单源最短路径为:"<<endl;
for(int j=1; j<= G->vn; j++) {
if(i==j) continue;
if(citys[j]==t1)
{ cout<<citys[i]<<"->"<<citys[j]<<": "<< G->arcs[i][j]<<endl;
}
}
}
MGraph *CreateMGraph()
{
int i, j, w;
MGraph *G;
int n,m;//顶点个数 边的个数
G = (MGraph *)malloc(sizeof(MGraph)); //建立图的存储结构
printf("\n输入图G的顶点个数 边数 ");
cin>>n>>m;
G->vn=n;
G->en=m;
for(i = 1; i <= G->vn; i++) {
G->vexs[i].no = i;
}
for(i = 1; i <= G->vn; i++)
for(j = 1; j <= G->vn; j++)
G->arcs[i][j] = INF;
for( i=1; i<=n; i++)
for( j=1; j<=n; j++)
G->arcs[i][j]=(i==j)?0:INF;
for( i=1; i<=m; i++) {
int t1,t2,d;//位置t1到位置t2的距离是d
cin>>t1>>t2>>d;
// G->arcs[t1][t2]=G->ar[t2][t1]=d;//无向图
G->arcs[t1][t2]=d;//-----------------有向图
}
printf("\n图的存储建立完毕!\n");
return G;
}
int main()
{
MGraph *G;
G = CreateMGraph();
Floyd(G);
Dijkstra(G);
Inquiry(G);
system("pause");
return 0;
}
六、程序测试及运行结果
5.1 运行实例一
(求给定有向图3-1的最短路径)
具体要求之一:求顶点 到其余顶点的最短路径;分别求顶点b到顶点d之间的最短路径、顶点 到顶点d之间的最短路径。
提示:为了操作方便,对于图的顶点都是用序号来表示的,所以顶点的字母就用其对应的序号来操作如 用1来代替用1来代替
5.2 运行实例二
(求给定无向图3-2的最短路径)
图3-2 一个简单的交通网络图
图3-2 是一个简单的交通网络图。
具体要求之一:求顶点“北京”到其余各城市之间的最短路径;并分别求“成都”到“上海”之间以及“上海”到“西安”之间的最短路径。
提示:为了操作方便,对于图的顶点都是用序号来表示的,所以顶点的城市名称就用其对应的编号来操作:如北京用1来代替,……。
七、心得体会
- 迪杰斯特拉算法是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,用一个数组int[ ]dist保存起点到其余各点的最短路径距离,采用贪心算法的策略,每次遍历到起点距离最近且未访问过的顶点的邻接节点并不断更新dist数组,直到扩展到终点为止。
- 弗洛伊德算法定义了两个二维矩阵:
矩阵D记录顶点间的最小路径
例如D[0][3]= 10,说明顶点0 到 3 的最短路径为10;
矩阵P记录顶点间最小路径中的中转点
例如P[0][3]= 1 说明,0 到 3的最短路径轨迹为:0 -> 1 -> 3。
它通过3重循环,k为中转点,v为起点,w为终点,循环比较D[v][w] 和 D[v][k] + D[k][w] 最小值,如果D[v][k] + D[k][w] 为更小值,则把D[v][k] + D[k][w] 覆盖保存在D[v][w]中。- 弗洛伊德算法作为求最短路径的经典算法,其算法实现相比迪杰斯特拉等算法是非常优雅的,可读性和理解都非常好。