数据结构与算法——第7章-图-移动迷宫小游戏-升级版(7.17)

一 概述

1
2
3
4
1.移动迷宫游戏升级
2.设计思路
3.二维数组转化成图
4.示例代码

二 移动迷宫游戏升级

1
2
3
4
5
6
在上一章的《移动迷宫》小游戏中,使用回溯法帮助骑士在迷宫中找到了通往出口的一条通路,
但是骑士并不太满意,他又提出了更高的需求。

《移动迷宫》升级版游戏简介:
迷宫只有两个门,一个入口,一个出口。一个骑士骑马从入口走进迷宫,迷宫中设置有很多墙壁,对前进方向造成障碍。
先需要你从迷宫中找到一条最短的通路,将行走路线和行走的最短距离告知骑士。

三 设计思路

1
2
3
4
类似于寻找最短路径这样的问题,最直接的方法就是使用迪杰斯特拉算法和弗洛伊德算法。

两种算法面对的数据结构是图,但是迷宫是在二维数组中进行存储的,
所以如果使用前面两种算法的话,需要首先将二维数组转化为图的存储形式。

四 二维数组转化成图

4.1 3*3 迷宫

1
2
如下图所示,此为 3*3 迷宫:
提示:S 为入口,E 为出口,# 为墙壁,- 为通路

4.2 转换思考

1
2
3
4
5
6
7
8
9
在编写程序向计算机中输入该迷宫的数据时,宜使用二维数组进行存储。
但是无论是迪杰斯特拉算法还是弗洛伊德算法,其处理对象都是有向网或者无向网。
迷宫中并不涉及到具体的方向,所以需要将存储迷宫的二维数组转化为无向网。

无向网的存储方式也是用二维数组来实现,将迷宫中所有的顶点看作是图中的顶点,
对于上图的迷宫来说,共有 9 个顶点,所以转化为无向网时,需要用 9*9 的一个二维数组来表示。

在转化时,从迷宫的左上角(上图的 S 开始),一行一行的进行转化,对于每个顶点来说,
只需要判断其右侧和相邻的下方顶点是否为通路,如果是通路,转化为图中的直接体现就是两顶点之间有线连接。

4.3 转换过程演示

一、步骤1

1
2
例如,上图中的 S 其右侧和下方的顶点都是 - ,骑士可以通过,
那在图中的表现就是 S 同其右侧顶点和下方顶点之间存储通路,如下图所示:

二、步骤2

1
2
对于图 1 中的二维数组,其完全转化为图,如下图所示
(每个顶点用其二维数组中的坐标来表示,00 表示第 0 行第 0 列):

三、步骤3

1
2
图 1 中的二维数组转化为图的存储表示如下图所示:
提示:1 表示有通路,0 表示没有通路,# 由于表示墙壁,同其它任何顶点之间都没有通路。

五 示例代码

5.1 使用迪杰斯特拉算法求迷宫的最短路径,其完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include <stdio.h>
#define MAX_VERtEX_NUM 1000 //顶点的最大个数
#define VRType int //表示弧的权值的类型
#define VertexType char //图中顶点的数据类型
#define INFINITY 65535
typedef enum{false,true} bool;
typedef struct {
VertexType vexs[MAX_VERtEX_NUM]; //存储图中顶点数据
VRType arcs[MAX_VERtEX_NUM][MAX_VERtEX_NUM]; //二维数组,记录顶点之间的关系
int vexnum,arcnum; //记录图的顶点数和弧(边)数
}MGraph;
typedef int PathMatrix[MAX_VERtEX_NUM]; //用于存储最短路径中经过的顶点的下标
typedef int ShortPathTable[MAX_VERtEX_NUM]; //用于存储各个最短路径的权值和

//迪杰斯特拉算法,v0表示有向网中起始点所在数组中的下标
void ShortestPath_Dijkstra(MGraph G,int v0,PathMatrix *p,ShortPathTable *D){
int final[MAX_VERtEX_NUM];//用于存储各顶点是否已经确定最短路径的数组
//对各数组进行初始化
for (int v=0; v<G.vexnum; v++) {
final[v]=0;
(*D)[v]=G.arcs[v0][v];
(*p)[v]=0;
}
//以起点为下标的顶点为起始点,所以不用再判断
(*D)[v0]=0;
final[v0]=1;
int k = 0;
for (int i=0; i<G.vexnum; i++) {
int min=INFINITY;
//选择到各顶点权值最小的顶点,即为本次能确定最短路径的顶点
for (int w=0; w<G.vexnum; w++) {
if (!final[w]) {
if ((*D)[w]<min) {
k=w;
min=(*D)[w];
}
}
}
//设置该顶点的标志位为1,避免下次重复判断
final[k]=1;
//对从起点到各顶点的权值进行更新
for (int w=0; w<G.vexnum; w++) {
if (!final[w]&&(min+G.arcs[k][w]<(*D)[w])) {
(*D)[w]=min+G.arcs[k][w];
(*p)[w]=k;//记录各个最短路径上存在的顶点
}
}
}
}
//在将二维数组转化为图的过程中,需要判断当前的点是否越界或者是否为通路
bool canUsed(int i,int j,int n,int m,char a[][110]){
if (a[i][j]!='#' && i>=0 && i<n && j>=0 && j<m) {
return true;
}
return false;
}
int main(){
char a[110][110];
int n,m;
scanf("%d %d",&n,&m);
getchar();
MGraph G;
G.vexnum=0;
G.arcnum=0;
//记录入口在图的顶点数组中的位置下标
int start =0;
//记录出口在图的顶点数组中的位置下标
int exit=0;
//初始化记录图的边的二维数组,假设各个边的长度为无穷大,即两顶点之间没有边
for (int i=0; i<n*m; i++) {
for (int j=0; j<n*m; j++) {
G.arcs[i][j]=INFINITY;
}
}
//输入二维数组,同时记录入口和出口的位置
for (int i=0; i<n; i++) {
for (int j=0; j<m; j++) {
scanf("%c",&a[i][j]);
G.vexs[i*m+j]=a[i][j];
G.vexnum++;
if (a[i]
[j]=='S') {
start=i*m+j;
}else if(a[i][j]=='E'){
exit=i*m+j;
}
}
getchar();//作用是为了读取缓存区中的换行符(因为迷宫是一行一行输入到内存中的)
}
//将二维数组转换为无向图,在转换时,从二维数组的左上角开始,每次判断当前顶点的右侧和下侧是否为通路,这样所有的通路就可以转换为无向图中的边。
for (int i=0; i<n; i++) {
for (int j=0; j<m; j++) {
//首先判断当前点是否为通路
if (canUsed(i, j, n, m, a)) {
if (canUsed(i+1, j, n, m, a)) {
//设定两顶点之间的边的权值为 1
G.arcs[i*m+j][(i+1)*m+j]=1;
G.arcs[(i+1)*m+j][i*m+j]=1;
G.arcnum++;
}
if (canUsed(i, j+1, n, m, a)) {
G.arcs[i*m+j][i*m+j+1]=1;
G.arcs[i*m+j+1][i*m+j]=1;
G.arcnum++;
}
}
}
}
PathMatrix P;
ShortPathTable D;
//进行迪杰斯特拉算法
ShortestPath_Dijkstra(G,start, &P, &D);
//如果最终记录的权值和还是无穷大,证明,入口和出口之间没有通路
if (D[exit]==INFINITY) {
printf("-1");
}else{
printf("入口到出口的最短路径长度为:\n");
printf("%d\n",D[exit]);
printf("入口到出口的最短路径为(逆序):\n");
printf("(%d,%d) ",exit/m,exit%m);
while (P[exit]!=0) {
printf("(%d,%d) ",P[exit]/m,P[exit]%m);
exit=P[exit];
}
printf("(%d,%d)\n",start/m,start%m);
}
return 0;
}

5.2 运行结果

1
2
3
4
5
6
7
8
9
10
程序输入:
3 3
S-#
---
##E
程序输出:
入口到出口的最短路径长度为:
4
入口到出口的最短路径为(逆序):
(2,2) (1,2) (1,1) (0,1) (0,0)

六 参考

  • C语言中文网—移动迷宫小游戏(升级版)