目录
1.综合应用已学过的数据结构与算法知识,实现课程设计。
2.加深对课程内容的理解,提高软件设计、编写及调试程序的能力。
3.提高对问题的抽象能力和算法的运用能力。
4.进一步理解最短路径算法的原理及并综合编程实践。
定义交通图的存储结构。邻接矩阵是表示图形中顶点之间相邻关系的矩阵。设G=(V,E)是具有n个顶点的图,则G的邻接矩阵是具有如下定义的n阶方阵。
注:一个图的邻接矩阵表示是唯一的,其表示需要用一个二维数组存储顶点之间相邻关系的邻接矩阵并且还需要用一个具有n个元素的一维数组来存储顶点信息(下标为i的元素存储顶点的信息)。
邻接矩阵的存储结构:
typedef struct
{
char name[50];
char intro[999];
int x, y;
}SITE;
typedef struct
{
SITE site[ALLNameNum];
int length[ALLNameNum][ALLNameNum];
}MAP;
最短路径问题:已知无向图(带权),期望找出从某个源点S∈V到G中其余各顶点的最短路径。Dijkstra算法即按路径长度递增产生诸顶点的最短路径算法。
算法原理:设有向图G=(V,E),其中V={1,2,……n},cost是表示G的邻接矩阵,length [i][j]表示有向边<i,j>的权。若不存在有向边<i,j>,则length [i][j] 的权为无穷大(这里取值为32767)。设S是一个集合,集合中一个元素表示一个顶点,从源点到这些顶点的最短距离已经求出。设顶点V1为源点,集合S的初态只包含顶点V1。数组dist记录从源点到其它各顶点当前的最短距离,其初值为dist[i]= length [i][j],i=2,……n。从S之外的顶点集合V-S中选出一个顶点w,使dist[w] 的值最小。于是从源点到达w只通过S中的顶点,把w加入集合S中,调整dist中记录的从源点到V-S中每个顶点v的距离:从原来的dist[v]和dist[w]+ length [w][v]中选择较小的值作为新的dist[v]。重复上述过程,直到S中包含V中其余顶点的最短路径。
最终结果是:S记录了从源点到该顶点存在最短路径的顶点集合,数组dist记录了从源点到V中其余各顶点之间的最短路径,path是最短路径的路径数组,其中path[i]表示从源点到顶点i之间的最短路径的前驱顶点。
一张青岛地图包括n个地点,假设地点间有m条路径,每条路径的长度已知。给定地图的一个起点和终点,利用Dijsktra算法求出起点到终点之间的最短路径。并且实现地点的查询,增加,删除以及更新操作,并且可视化整张地图,基于Dijkstra算法实现最短路径的查找与规划,完成地点无向图的构建,采用邻接矩阵的存储方式并实现以下功能:
1、输出所有地点及其介绍
2、查询某一个地点及其介绍
3、增加一个地点
4、删除一个地点
5、更新一个地点
6、增加一条路
7、删除一条路
8、更新一条路
9、查询某一地点到其他所有地点的最短路径
10、查询某两个地点之间的最短路径
11、输出地图
1.界面友好:有合理的中文提示,每个功能可以设立菜单,根据提示,可以完成相关的功能。出现非法输入,会给出异常提示。
2.物理存储:相关数据要求存储在某一数据结构中,在程序中完成数据结构的定义与说明。
3.逻辑结构:根据问题的要求,采用线性或非线性结构。实验分析中考虑数据量问题。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include <graphics.h>
using namespace std;
#define ALLNameNum 99
#define INF 99999
typedef int dist[ALLNameNum];
typedef int path[ALLNameNum];
typedef struct
{
char name[50];
char intro[999];
int x, y;
}SITE;
typedef struct
{
SITE site[ALLNameNum];
int length[ALLNameNum][ALLNameNum];
}MAP;
MAP M;
int N = 0;
path p;
dist d;
void init()
{
int i, j;
strcpy(M.site[1].name, "胶东机场");
strcpy(M.site[1].intro, "青岛胶东国际机场(Qingdao Jiaodong International Airport),位于中国山东省青岛市胶州市胶东街道前店口村");
strcpy(M.site[2].name, "红岛火车站");
strcpy(M.site[2].intro, "山东省青岛市城阳区红岛经济区河套街道汇海路南侧");
strcpy(M.site[3].name, "流亭");
strcpy(M.site[3].intro, "青岛流亭国际机场(已关闭)");
strcpy(M.site[4].name, "青岛站");
strcpy(M.site[4].intro, "山东省青岛市市南区泰安路2号");
strcpy(M.site[5].name, "青岛北站");
strcpy(M.site[5].intro, "山东省青岛市李沧区静乐路1号");
strcpy(M.site[6].name, "李村");
strcpy(M.site[6].intro, "李村隶属于山东省青岛市李沧区");
strcpy(M.site[7].name, "五四广场");
strcpy(M.site[7].intro, "五四广场(May Fourth Square),位于山东省青岛市市南区东海西路");
strcpy(M.site[8].name, "中山公园");
strcpy(M.site[8].intro, "青岛最大的综合性公园");
strcpy(M.site[9].name, "麦岛");
strcpy(M.site[9].intro, "麦岛是山东省青岛市区沿岸第一大岛");
strcpy(M.site[10].name, "中心医院");
strcpy(M.site[10].intro, "青岛市中心医院,青岛市中心医院位于山东省青岛市四方区四流南路127号");
strcpy(M.site[11].name, "QAU");
strcpy(M.site[11].intro, "青岛农业大学(Qingdao Agricultural University),位于山东省青岛市");
strcpy(M.site[12].name, "台东");
strcpy(M.site[12].intro, "台东区是山东省青岛市原辖区,地名沿用自原区划台东镇。");
strcpy(M.site[13].name, "遵义路");
strcpy(M.site[13].intro, "山东省青岛市李沧区");
strcpy(M.site[14].name, "鳌山卫");
strcpy(M.site[14].intro, "鳌山卫镇位于即墨市东隅,镇机关驻地距即墨市政府所在地20公里。");
strcpy(M.site[15].name, "苗岭路");
strcpy(M.site[15].intro, "苗岭路站(Miaoling Rd Station),位于中国山东省青岛市深圳路与苗岭路交叉口南侧");
strcpy(M.site[16].name, "SDU");
strcpy(M.site[16].intro, "山东大学(青岛)位于山东省青岛市即墨区“蓝色硅谷”核心区");
strcpy(M.site[17].name, "OUC");
strcpy(M.site[17].intro, "中国海洋大学(Ocean University of China,OUC),位于山东省青岛市");
for (i = 1; i <= ALLNameNum; i++)
{
for (j = 1; j <= ALLNameNum; j++)
{
M.length[i][j] = INF;
}
}
for (i = 1; i <= ALLNameNum; i++)
M.length[i][i] = 0;
M.length[1][2] = M.length[2][1] = 30;
M.length[1][3] = M.length[3][1] = 30;
M.length[2][3] = M.length[3][2] = 90;
M.length[2][4] = M.length[4][2] = 70;
M.length[3][5] = M.length[5][3] = 50;
M.length[3][6] = M.length[6][3] = 50;
M.length[4][5] = M.length[5][4] = 50;
M.length[4][7] = M.length[7][4] = 100;
M.length[5][6] = M.length[6][5] = 30;
M.length[6][7] = M.length[7][6] = 110;
M.length[6][10] = M.length[10][6] = 20;
M.length[7][8] = M.length[8][7] = 30;
M.length[7][9] = M.length[9][7] = 30;
M.length[7][10] = M.length[10][7] = 30;
M.length[8][9] = M.length[9][8] = 30;
M.length[9][10] = M.length[10][9] = 60;
M.length[9][11] = M.length[11][9] = 40;
M.length[10][11] = M.length[11][10] = 40;
M.length[11][12] = M.length[12][11] = 100;
M.length[11][13] = M.length[13][11] = 50;
M.length[12][13] = M.length[13][12] = 60;
M.length[12][15] = M.length[15][12] = 30;
M.length[12][17] = M.length[17][12] = 170;
M.length[13][14] = M.length[14][13] = 70;
M.length[13][15] = M.length[15][13] = 30;
M.length[13][16] = M.length[16][13] = 50;
M.length[14][16] = M.length[16][14] = 50;
M.length[15][16] = M.length[16][15] = 20;
M.length[16][17] = M.length[17][16] = 30;
N = 17;
M.site[1].x = 50;
M.site[1].y = 25;
M.site[2].x = 150;
M.site[2].y = 125;
M.site[3].x = 250;
M.site[3].y = 25;
M.site[4].x = 150;
M.site[4].y = 475;
M.site[5].x = 350;
M.site[5].y = 175;
M.site[6].x = 475;
M.site[6].y = 50;
M.site[7].x = 475;
M.site[7].y = 475;
M.site[8].x = 575;
M.site[8].y = 650;
M.site[9].x = 675;
M.site[9].y = 525;
M.site[10].x = 575;
M.site[10].y = 150;
M.site[11].x = 750;
M.site[11].y = 225;
M.site[12].x = 575;
M.site[12].y = 525;
M.site[13].x = 575;
M.site[13].y = 225;
M.site[14].x = 1075;
M.site[14].y = 25;
M.site[15].x = 1000;
M.site[15].y = 375;
M.site[16].x = 1100;
M.site[16].y = 225;
M.site[17].x = 1175;
M.site[17].y = 325;
}
void queryAllSite()
{
int t;
printf("所有地点如下:\n");
for (t = 1; t <= N; t++)
{
printf("编号:%d\n 地点:%s\n 介绍:%s\n", t, M.site[t].name, M.site[t].intro);
}
printf("\n任意键返回");
getch();
}
void addSite()
{
char s1[30], s2[200]; //地点名称;地点介绍
int t, i, q, p, x, y; //t为与该地点共有几个地点相连;i为应第几个地点与之相连;
//q为与之相连的地点;p为这个地点和新地点的距离
//x为此地点位置的x值;y为此地点位置的y值
N++;
printf("请输入该地点的名称:");
scanf("%s", &s1);
printf("请输入该地点的介绍:");
scanf("%s", &s2);
printf("请输入该地点的位置的x值:");
scanf("%d", &x);
printf("请输入该地点的位置的y值:");
scanf("%d", &y);
strcpy(M.site[N].name, s1);
strcpy(M.site[N].intro, s2);
M.site[N].x = x;
M.site[N].y = y;
printf("请输入共有几个地点与此地点相通:");
scanf("%d", &t);
for (i = 1; i <= t; i++)
{
printf("第%d个地点与之相通;", i);
scanf("%d", &q);
printf("他们之间的距离为:");
scanf("%d", &p);
M.length[N][q] = M.length[q][N] = p;
}
printf("\n按任意键返回!");
getch();
}
void delSite()
{
printf("请输入要删除的地点序号数");
int t, p, Q;
scanf("%d", &p);
Q = p;
if (p <= N)
{//N节点数量
for (t = 1; t <= N; t++)
for (p = p; p <= N; p++)
{
M.length[t][p] = M.length[t][p + 1];
M.length[p][t] = M.length[p + 1][t];
}
for (Q; Q < N; Q++)
M.site[Q] = M.site[Q + 1];
printf("删除成功");
}
else
printf("删除失败");
N--;
printf("\n按任意键返回!");
getch();
}
void reSite()
{
int t;
char s1[30], s2[200];
printf("请输入要更新的地点编号:");
scanf("%d", &t);
if (t > N)
printf("此地点不存在,更新失败");
else
{
printf("请输入地点名称:");
scanf("%s", &s1);
printf("请输入地点介绍:");
scanf("%s", &s2);
strcpy(M.site[t].name, s1);
strcpy(M.site[t].intro, s2);
printf("更新完成!");
}
printf("\n按任意键返回!");
getch();
}
void addpath()
{
int a, b, c;
printf("请问要在那两个地点之间增加一条路\n");
printf("请输入第一个地点编号:");
scanf("%d", &a);
printf("请输入第二个地点编号:");
scanf("%d", &b);
printf("请输入这条路的长度:");
scanf("%d", &c);
if (a > N || b > N)
printf("地点超出了已有地点范围,路径增加失败!");
else
{
M.length[a][b] = M.length[b][a] = c;
printf("路径增加成功!");
}
printf("\n按任意键返回!");
getch();
}
void delpath()
{
int a, b;
printf("删除路径的编号\n");
printf("请输入第一个地点编号:");
scanf("%d", &a);
printf("请输入第二个地点编号:");
scanf("%d", &b);
if (a > N || b > N)
printf("越界,路径删除失败!");
else
{
M.length[a][b] = M.length[b][a] = INF;
printf("路径删除成功!");
}
printf("\n按任意键返回!");
getch();
}
void repath()
{
int a, b, c;
printf("请问要更新那两个地点之间的路\n");
printf("请输入第一个地点编号:");
scanf("%d", &a);
printf("请输入第二个地点编号:");
scanf("%d", &b);
printf("请输入更新后的长度:");
scanf("%d", &c);
if (a > N || b > N)
printf("这条路不存在,路径更新失败!");
else
{
M.length[a][b] = M.length[b][a] = c;
printf("路径更新成功!");
}
printf("\n按任意键返回!");
getch();
}
void querySite()
{
int a;
printf("请问您要查询的地点编号是:");
scanf("%d", &a);
if (a > N)
printf("查询的地点不存在,查询失败!");
else
printf("编号:%d\n 地点:%s\n 介绍:%s\n", a, M.site[a].name, M.site[a].intro);
printf("\n按任意键返回!");
getch();
}
void dijkstraAllSite()
{
int v0;
printf("请输入查询的地点:");
scanf("%d", &v0);
boolean flag[ALLNameNum];
//v表示上一个节点,k表示遍历节点
int i, k, j, v, min, x;
for (v = 1; v <= N; v++)
{
flag[v] = FALSE;
d[v] = M.length[v0][v];
if (d[v] < INF && d[v] != 0)
p[v] = v0;
else
p[v] = -1;
}
flag[v0] = TRUE;
d[v0] = 0;//原点距离
for (i = 2; i <= N; i++)
{
min = INF;
for (k = 1; k <= N; ++k)
if (!flag[k] && d[k] < min)
{//没有被查询过并且距离小于最小值则更新
v = k;
min = d[k];
}
if (min == INF)
return;
flag[v] = TRUE;
for (k = 1; k <= N; ++k)
if (!flag[k] && (min + M.length[v][k] < d[k]))
{
d[k] = min + M.length[v][k];//到k的距离
p[k] = v;//k节点的上一个节点是v
}
}
}
void printAllSite()
{
int st[ALLNameNum], i, pre, top = -1;
for (i = 1; i <= N; i++)
{
printf("\n到达地点%2d的总距离为: %5d , 经过路径为:", i, d[i]);
st[++top] = i;
pre = p[i];
while (pre != -1)
{
st[++top] = pre;
pre = p[pre];
}
while (top > 0)
{
printf("%d", st[top--]);
if (top > 0)
printf("--->");
}
}
getch();
}
void dijkstraTwoSite()
{
int v0;
printf("请输入起始地点:");
scanf("%d", &v0);
boolean flag[ALLNameNum];
int i, k, j, v, min, x;
for (v = 1; v <= N; v++)
{
flag[v] = FALSE;
d[v] = M.length[v0][v];
if (d[v] < INF && d[v] != 0)
p[v] = v0;
else
p[v] = -1;
}
flag[v0] = TRUE;
d[v0] = 0;
for (i = 2; i <= N; i++)
{
min = INF;
for (k = 1; k <= N; ++k)
if (!flag[k] && d[k] < min)
{
v = k;
min = d[k];
}
if (min == INF)
return;
flag[v] = TRUE;
for (k = 1; k <= N; ++k)
if (!flag[k] && (min + M.length[v][k] < d[k]))
{
d[k] = min + M.length[v][k];
p[k] = v;
}
}
}
void printTwoSite()
{
int y;
printf("\n请输入目的地点:");
scanf("%d", &y);
int st[ALLNameNum], i, pre, top = -1;
for (i = 1; i <= N; i++)
{
if (i == y)
printf("\n总距离是: %5d , 经过路径为:", d[i]);
st[++top] = i;
pre = p[i];
while (pre != -1)
{
st[++top] = pre;
pre = p[pre];
}
while (top > 0)
{
if (i == y)
{
printf("%d", st[top--]);
if (top > 0)
printf("--->");
}
else
top--;
}
}
getch();
}
void printGraph()
{
int i, j;
char s[100];
strcpy(s, "交通地图");
initgraph(1200, 800);
for (i = 1; i <= N; i++)
{
setfillcolor(RGB(225, 236, 0));
fillcircle(M.site[i].x, M.site[i].y, 20);
outtextxy(M.site[i].x, M.site[i].y, LPCTSTR(M.site[i].name));
}
for (i = 1; i <= N; i++)
for (j = 1; j <= N; j++)
{
if (M.length[i][j] > 0 && M.length[i][j] < 1000)
line(M.site[i].x, M.site[i].y, M.site[j].x, M.site[j].y);
}
outtextxy(900, 650, LPCTSTR(s));
getch();
closegraph();
}
main()
{
init();
int x;
while (1)
{
printf("**********************************************************************\n");
printf("* 欢迎使用基于交通路线的规划系统 *\n");
printf("**********************************************************************\n");
printf("\n 0、退出程序 ");
printf("\n 1、输出所有地点及其介绍");
printf("\n 2、查询某一个地点及其介绍");
printf("\n 3、增加一个地点");
printf("\n 4、删除一个地点");
printf("\n 5、更新一个地点");
printf("\n 6、增加一条路");
printf("\n 7、删除一条路");
printf("\n 8、更新一条路");
printf("\n 9、查询某一地点到其他所有地点的最短路径");
printf("\n 10、查询某两个地点之间的最短路径 ");
printf("\n 11、输出地图 目前地点数:%d\n", N);
printf("请输入选项:");
scanf("%d", &x);
if (x == 0)
break;
else
switch (x)
{
case 1:queryAllSite(); break;
case 2:querySite(); break;
case 3:addSite(); break;
case 4:delSite(); break;
case 5:reSite(); break;
case 6:addpath(); break;
case 7:delpath(); break;
case 8:repath(); break;
case 9:dijkstraAllSite(); printAllSite(); break;
case 10:dijkstraAllSite(); printTwoSite(); break;
case 11:printGraph(); break;
}
system("cls");
}
}
运行程序如下所示:
分别进行功能测试:
1.输出所有地点及其介绍,结果如下所示:
2.查询某一个地点及其介绍,结果如下所示:
3.增加一个地点,结果如下所示:
这里添加样例为:信息科学与工程学院
4.删除一个地点,结果如下所示:
5.更新一个地点,结果如下所示:
6.增加一条路,结果如下所示:
7.删除一条路,结果如下所示:
8.更新一条路,结果如下所示:
9.查询某一地点到其他所有地点的最短路径,结果如下所示:
这里采用了Dijkstra算法,求解两点之间的最短路径,并且采用栈的方式输出该条路径。循环实现所有路径的输出。
10.查询某两个地点之间的最短路径,结果如下所示:
这里采用了Dijkstra算法,求解两点之间的最短路径,并且采用栈的方式输出该条路径。
11.输出地图,结果如下所示:
这里采用了graphics.h头文件库,针对地点的xy坐标属性,可视化无向图。
本课程设计是有关于基于交通路线的规划系统的实现,实验帮助我复习了有关于图的知识以及最短路径里的Dijkstra算法,在课设的选题方面上参考了一些优秀的思路,最后整个系统由我自行实现。
整个设计的核心思路是Dijkstra算法的实现,其基本思想是,设置顶点集合S并不断地作贪心选择来扩充这个集合。一个顶点属于集合S当且仅当从源到该顶点的最短路径长度已知。初始时,S中仅含有源。设u是G的某一个顶点,把从源到u且中间只经过S中顶点的路称为从源到u的特殊路径,并用数组dist记录当前每个顶点所对应的最短特殊路径长度。Dijkstra算法每次从V-S中取出具有最短特殊路长度的顶点u,将u添加到S中,同时对数组dist作必要的修改。一旦S包含了所有V中顶点,dist就记录了从源到所有其他顶点之间的最短路径长度。
此外,在路径输出功能函数上的设计,我采用了栈存储的方式,实现了路径溯源与倒序输出,较好的完成了路径的输出与追溯。对于增删改查功能的设计上采用了增删数组元素的基本思想,即将要删除的数组元素的右侧以及下侧的移动来实现节点的增删,路径的删除则是采用有关图的定义,将其路径重新设置为无穷。
有关于整个无向图的可视化部分,我采用了graphics.h图形库来实现,通过查阅了相关的博客学会了使用这个库,但是还是存在一些问题,这graphics.h图形库有关于outtextxy函数的重载的两个参数定义并不能很好地显示中文,关于这个问题还需要我进一步的探究。
此外,该课程设计还是存在许多不足,例如对于邻接矩阵实现存储的方式对于空间的复杂度要求过高,可以进一步采用邻接表、链表等存储结构,可以大大地降低空间复杂度。其次,在时间复杂度方面,可以很清晰地看到由于是线性查找,Dijkstra的时间复杂度是O(n2),效率并不高,我通过查阅文献的方式了解到可以进一步采用优先队列的思路优化它,在存储时就按照从小到大的顺序实现,这样在选择节点时直接取队首距离最小的节点即可,可以将时间复杂度优化到O(logn)左右。还有对于地点的设计,使用坐标xy只是用来graphics.h可视化的,并未考虑到实际距离的限制,这样可能会造成坐标差距很大,但是距离却很近等异常情况,因此可以通过计算坐标之间的欧氏距离,并将它设置为两点距离的最小值作为限制,防止异常情况的发生。
最后,整个课程设计的过程十分不易,从最初的选题,选定思路,函数模块设计,接口的对接,bug的调试耗费了很多心血,但是最终可以呈现出一个相对完整的项目还是令人喜悦的。
1.graphics.h图形库用法总结
graphics.h图形库用法总结_i-Curve的博客-CSDN博客_graphics.h
2.graphic头文件函数_使用graphics.h来绘制图形
graphic头文件函数_使用graphics.h来绘制图形_weixin_39791653的博客-CSDN博客
3.Dijkstra算法应用(二) Emergency
Dijkstra算法应用(二) Emergency_Robbery07的博客-CSDN博客
4. 吴红波,王英杰,杨肖肖.基于Dijkstra算法优化的城市交通路径分析[J].北京交通大学学报,2019,43(04):116-121+130.
5.Visualization of shortest path
https://github.com/Utsav-Unadkat/CG-PROJECT#visualization-of-shortest-path
6. 基于最短路径的武汉地铁规划系统
https://github.com/wsxk/WuHan_Subway
7. HitWH_Map