校园导游咨询(基于迪杰斯特拉算法求任意两点间的最短路径,并把最短路径输出)

实验说明

[实验目的]
掌握图的存储方法和最短路径算法。
[实验内容]
设计一个校园导游程序,为来访的客人提供各种信息查询服务。测试数据根据实际情况指定。提示:一般情况下,校园的道路是双向通行的,可设校园平面图是一个无向图。顶点和边均含有相关信息。
[实验要求]
(1)设计你的学校的校园平面图,所含景点不少于10个。以图中顶点表示校内各景点,存放景点名称、代号、简介等信息;以边表示路径,存放路径长度等相关信息。
(2)为来访客人提供图中任意景点相关信息的查询。
(3)为来访客人提供图中任意景点的问路查询,即查询任意两个景点之间的一条最短的简单路径。

迪杰特斯拉算法

对于如图所示的平面图,首先写出它的邻接矩阵
在这里插入图片描述
邻接矩阵arcs[11][11](∞代表两个顶点间没有边,数字为两顶点间边的权值)
在这里插入图片描述
假设从v0出发,找vo到其余十一个顶点的最短路径,那么数组D[ ] =arcs[0][ ]。数组D是一个辅助向量,它的每个分量D[i]表示当前所找到的从始点v0到每个终点vk的最短路径。刚开始执行迪杰斯特拉时,初始化数组D[ ] = {∞ , 2, ∞ , ∞, ∞ , 6, ∞, ∞ , ∞ , ∞ , ∞, ∞},从D中选取权值最小的点,作为第一条最短路径的终点,显然,此时应该选取点v1,把v1放入数组S中,表示v1这个点已经选取过了,避免重复选取同一个点作为最短路径的终点。为了程序描述起来方便,把每次选取的最短路径的终点都保存在变量min中,显然,此时min = v1,S = {v1},D[ ] = {∞ , 2, ∞ , ∞, ∞ , 6, ∞, ∞ , ∞ , ∞ , ∞, ∞},D中把2斜体加粗说明下次选权值最小的点时就不能再选择边(v0,v1)了。
按照迪杰斯特拉算法的核心思想,下一条最短路径的终点vj要么与v0直接相连,此时最短路径为(v0,vk),要么与min直接相连,此时最短路径为(v0,min)∪(min,vk)。现在需要做的工作就是判断D[k]与D[min] + arcs[min][k]的大小。若D[min] + arcs[min][k]小于D[k],说明从始点v0到到vk经过顶点Vmin的路径要短于直接从V0到Vk的路径。那么此时需要执行语句D[k] = D[min] + arcs[min][k],对最短路径进行更新。k取遍所有的顶点为一次循环(始点V0除外,否则可能会形成一个包含顶点V0的回路)。

第一次循环:
由于此时min = v1,所以比较D[1] + arcs[1][k]和D[k]的大小关系,若D[1] + arcs[1][k]小于D[k],说明从始点v0到终点vk经过顶点v1的路径要短于直接从V0到Vk的路径。那么此时需要执行语句D[k] = D[1] + arcs[1][k],对最短路径进行更新。循环结束之后,数组D = {∞ , 2, 6 , ∞, ∞ , 6, ∞, ∞ , ∞ , ∞ , ∞, ∞},现在需要从D中选取权值最小的点作为最短路径的终点,可以看到,D中有两个6,这种情况下选哪一个都可以,取决于你的选择函数是怎么写的,我的选择函数选的是第一个6,所以此时min = v2,S = {v1,v2}, D = {∞ , 2, 6 , ∞, ∞ , 6, ∞, ∞ , ∞ , ∞ , ∞, ∞},进入下一次循环。
第二次循环:
过程和第一次循环一样,只不过是现在需要比较D[2] + arcs[2][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, ∞ , ∞ , ∞ , ∞, ∞},从D中选择最小值,min = v5,S = {v1,v2,v5},D = {∞ , 2, 6 , 9, 8 , 6, 9, ∞ , ∞ , ∞ , ∞, ∞}。
第三次循环:
比较D[5] + arcs[5][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, ∞ , 13 , ∞ , ∞, ∞},从D中选择最小值,min = v4,S = {v1,v2,v5,v4},D = {∞ , 2, 6 , 9, 8 , 6, 9, ∞ , 13 , ∞ , ∞, ∞}
第四次循环:
比较D[4] + arcs[4][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , ∞ , ∞, ∞},从D中选择最小值,min = v3,S = {v1,v2,v5,v4,v3},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , ∞ , ∞, ∞}
第五次循环:
比较D[3] + arcs[3][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , ∞ , ∞, ∞},从D中选择最小值,min = v6,S = {v1,v2,v5,v4,v3,v6},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , ∞ , ∞, ∞}
第六次循环:
比较D[6] + arcs[6][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , 15 , ∞, ∞},从D中选择最小值,min = v7,S = {v1,v2,v5,v4,v3,v6,v7},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , ∞, ∞}
第七次循环:
比较D[7] + arcs[7][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , 15 , ∞, ∞},从D中选择最小值,min = v8,S = {v1,v2,v5,v4,v3,v6,v7,v8},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , ∞, ∞}
第八次循环:
比较D[8] + arcs[8][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , 15 , ∞, 15},从D中选择最小值,min = v9,S = {v1,v2,v5,v4,v3,v6,v7,v8,v9},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , ∞, 15}
第九次循环:
比较D[9] + arcs[9][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10 , 13 , 15 , 16, 15},从D中选择最小值,min = v11,S = {v1,v2,v5,v4,v3,v6,v7,v8,v9,v11},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , 16, 15}
第10次循环:
比较D[11] + arcs[11][k]和D[k]的大小,循环结束之后,数组D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , 16, 15},从D中选择最小值,min = v10,S = {v1,v2,v5,v4,v3,v6,v7,v8,v9,v11,v10},D = {∞ , 2, 6 , 9, 8 , 6, 9, 10, 13 , 15 , 16, 15}
迪杰斯特拉算法结束!

程序分析

景点为自定义的结构体类型,包括景点名称,代码,景点介绍三部分。

struct Scenic_sports {
	char name[15];
	int code;
	char info[100];
};

用结构体数组存放全部的12个景点,每个景点相当于无向图的一个顶点,景点的代码是数组的下标,同时也是该景点对应顶点的编号。在主函数中初始化景点数组。
当用户选择寻找景点间最短路径功能时,调用get_shortest_path函数,在此函数中,对景点的邻接矩阵arcs进行初始化。定义一个名为predecessor的数组,用于存储最短路径上每个顶点的前驱,这样做可以很方便的输出找到的最短路径。举个例子,比如说在某次循环中,D[4] + arcs[4][7] < D[7],成立,那么下一步就要执行D[7] = D[4] + arcs[4][7],相当于是把v7这个点加入到当前找到的最短路径中,再下一步执行predecessor[7] = 4,这样就建立起了v7的前驱。当然,如果后续循环中有D[7] + arcs[7][k] < D[k]成立,那么v7就会成为vk的前驱,即执行语句predecessor[k] = 7。
我们可以通过访问v7的前驱找到v4,而v4的前驱有两种可能,如果v4的前驱不是起始点,那么v4一定是通过D[min] + arcs[min][4] < D[4]条件成立才加入到最短路径中的。也就是说v4的前驱也已经存放在predecessor[4]中了。如果v4的前驱是起始点,我们是没法通过上面的条件语句建立v4的前驱的,但我们可以通过另一种方法来解决这个问题,开始时,把所有顶点的前驱都初始化成起始点即可完美解决。
注意:假如要查询从景点2到景点9的最短路径,始点设为景点2,根据前驱数组输出图中经过的景点时,只能先去找景点9的前驱,然后再一直往前索引,直到索引到景点2,这样输出的路径是逆序的,也就是说输出的是从景点9到景点2的路径。我们换一种思路,当用户要查询景点2到景点9的路径时,我们把始点设为景点9,这样最终输出的路径就是景点2到景点9的路径。

源码


#include "pch.h"   
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 12
#define MAX_NUM 10000000
//景点结构体
struct Scenic_sports {
	char name[15];
	int code;
	char info[100];
};
//查询景点信息
void get_info(Scenic_sports position[]) {
	printf("请输入要查询的景点代号:");
	int code;
	scanf("%d", &code);
	printf("%s", position[code].info);
}
//判断当前顶点是否在数组S中,也就是说是否已经被选择过
bool has_selected(int S[], int i, int k) {
	for (int j = 0; j < k; j++)
		if (i == S[j])
			return true;
	return false;
}
//从数组D中选取最小值
int search_min(int D[], int S[], int k) {	//k为数组S中元素的个数,也就是说现在已经选了k条最短路径
	int min = -1;	//若为非连通图,则返回-1
	int m = MAX_NUM;
	for (int i = 0; i < MAX_SIZE; i++) 
		if (!has_selected(S, i, k) && (D[i] < m)) {
			min = i;
			m = D[i];
		}
	
	return min;
}
//寻找最短路径
void get_shortest_path(Scenic_sports position[]) {
	int arcs[MAX_SIZE][MAX_SIZE];	//邻接矩阵,里面存放着边的权值
//初始化邻接矩阵
	for (int i = 0; i < MAX_SIZE; i++)
		for (int j = 0; j < MAX_SIZE; j++)
			arcs[i][j] = MAX_NUM;
	arcs[0][1] = arcs[1][0] = 2;
	arcs[1][2] = arcs[2][1] = 4;
	arcs[2][3] = arcs[3][2] = 3;
	arcs[2][6] = arcs[6][2] = 3;
	arcs[2][4] = arcs[4][2] = 2;
	arcs[0][5] = arcs[5][0] = 6;
	arcs[4][5] = arcs[5][4] = 3;
	arcs[4][7] = arcs[7][4] = 2;
	arcs[5][8] = arcs[8][5] = 7;
	arcs[7][8] = arcs[8][7] = 5;
	arcs[6][9] = arcs[9][6] = 6;
	arcs[9][10] = arcs[10][9] = 1;
	arcs[10][11] = arcs[11][10] = 1;
	arcs[11][8] = arcs[8][11] = 2;

	int min = 0;	//当前寻找到的最短路径的终点
	int adr1, adr2;		//查询从景点adr1到景点adr2的最短路径
	int D[MAX_SIZE] = { 0 };	//它的每个分量D[i]表示当前所找到的从始点adr1到每个终点vi的最短路径的长度
	int S[MAX_SIZE] = { 0 };	//已求得最短路径的终点的集合
	int predecessor[MAX_SIZE];	//存放顶点的前驱,方便输出最短路径,前驱的引入是本程序最重要的地方


	printf("请输入两个景点的代码:");
	scanf("%d %d",&adr1,&adr2);
	//对输入的两个景点进行交换,否则输出景点路径时为逆序输出
	int temp;
	temp = adr1;
	adr1 = adr2;
	adr2 = temp;

	for (int i = 0; i < MAX_SIZE; i++)	//初始化所有顶点的前驱都为起始景点
		predecessor[i] = adr1;

	for (int j = 0; j < MAX_SIZE; j++)
		D[j] = arcs[adr1][j];

	for (int i = 0; i < MAX_SIZE; i++) {
		for (int j = 0; (j < MAX_SIZE); j++) {
			if (j == adr1)	//防止寻找到的路径的起始点和终点相同
				continue;
			if (D[min] + arcs[min][j] < D[j]) {
				D[j] = D[min] + arcs[min][j];
				predecessor[j] = min;	//	前驱的建立
			}
		}
		min = search_min(D, S, i);	//	寻找当前最短路径,返回最短路径的终点

		if (min == -1) {	
			printf("非连通图,最短路径查询失败\n");
			exit(0);
		}

		S[i] = min;		//编号为min的顶点作为最短路径的终点,所以把此顶点放入S中
		if (min == adr2)	//如果现在选择的最短路径的终点恰好是adr2,则停止寻找最短路径,减少时间复杂度
			break;

	}
	//输出最短路径的信息
	int pre = adr2;		//注意,前面对adr1和adr2进行了交换
	printf("从%s到%s的路径长度为%d,现在输出路径信息\n", position[adr2].name,position[adr1].name,D[adr2]);
	for (int i = 0; pre != adr1; i++) {
		printf("%s-->",position[pre].name);
 		pre = predecessor[pre];
	}
	printf("%s", position[adr1].name);

}
int main()
{
	Scenic_sports position[MAX_SIZE] = {
		{"北门",0,"北门外原先有个集,后来政府说没有它就没有了"},
		{"校医院",1,"医术不敢恭维,但报销之后药挺便宜的,小病可以去校医院看"},
		{"北区公寓",2,"三个宿舍区域之一,离教学区最近,早上可以起的比东区南区晚一点"},
		{"北操",3,"来凑个数"},
		{"餐厅",4,"吃饭的地方,有一餐,二餐和清真"},
		{"东操",5,"凑数+1"},
		{"九球广场",6,"九个石头蛋,一个没有广场舞的广场"},
		{"五子顶",7,"海大崂山校区海拔最高的地方,有机会要爬上去看一看"},
		{"保研路",8,"暂无官方介绍"},
		{"行远楼",9,"海大行远楼,一跃解千愁,是一座行政楼"},
		{"孔子像",10,"每到期末考试的时候,孔子总是会收到很多贡品"},
		{"图书馆",11,"在里面自习比较宽敞,建议搬到北区"}
	};
	printf("********欢迎来到中国海洋大学景点查询系统********\n");
	printf("学校景点如下\n");
	for (int i = 0; i < MAX_SIZE; i++) 
		printf("代码:%d,名称:%s\n", position[i].code, position[i].name);
	
	printf("查询景点详细信息请按1,查询景点间的最短路径请按2\n");
	int func;
	scanf("%d", &func);
	if (func == 1)
		get_info(position);
	else if (func == 2)
		get_shortest_path(position);

}


引入前驱的思想不是我的原创,参考博客:https://blog.csdn.net/littlehedgehog/article/details/1758701
如果程序中有bug或者博客里有写错的地方,欢迎大家批评指正。

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值