栈和队列应用:迷宫问题

迷宫寻路#

应用情景#

例如如图所示迷宫,黄色方格代表起点,橙色方格代表终点,绿色方格代表可走路径,蓝色方格代表障碍物。已知这是一个 M × N 大小的迷宫,可以用 0 表示可走路径,1 表示障碍,算法要求实现从迷宫的任意一点出发,试探出一条通向终点的路径。

应用解析#

刚看到这个情景,我们是一头雾水的,因此在开始解析之前,我们先把迷宫的构成说明白。如图是一个我已经鸽了很久的 RPG 游戏制作页面,我们发现这个页面和游戏的界面是不一样的,游戏的舞台被一个个方格所切割,制作这类的游戏时,我无论是绘制地图、设置事件还是踩雷遇怪,都是通过对这些方格填充内容实现的,而这些方格在一张确定大小的地图上都是有对应坐标的。

我们是怎么定位可控角色在地图的位置的?其实也是通过这些坐标,确定好角色所在的方格之后就把角色的贴图填充进去。当角色进行移动时,我们先获取这个角色的坐标,确定移动到哪个方格之后,播放设置好的行走图即可实现。不知道爱玩游戏的你是否想过这些问题呢?(笑)

深度优先#

现在我们已经把背景说明白了,再来思考一下,我们可以把需求的迷宫描述为一个二维数组,二维数组抽象成几何图形的时候是一个矩阵,因此我们的问题就变成了描述起点到终点坐标的问题了。这个时候我们就要模拟一个玩家,这个玩家要标记他走过的坐标,由于要自动寻路,有东南西北四个维度,因此我们就以这个顺序先作为玩家的寻路顺序。当玩家遇到死路时,就要退回之前走过的路,因此我们就发现了栈结构“后进先出”的特性很适合用于描述走回头路的过程。

代码实现#

Copy Highlighter-hljs
#include<iostream> #include<stack> using namespace std; #define M 8 #define N 8 typedef struct { int x; //路径的 x 坐标 int y; //路径的 y 坐标 int next_direction = 1; //表示方位,由 1 到 4 分别表示东南西北 } unit; void Labyrinth(int xi, int yi, int xe, int ye); //走迷宫函数 void exploreWay(int x, int y, stack<unit>& path, unit& a_unit); //探路函数 int a_maze[M + 2][N + 2] = { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,0,1,1,0,0,1}, {1,0,1,1,1,0,0,0,0,1}, {1,0,0,0,1,0,0,0,0,1}, {1,0,1,0,0,0,1,0,0,1}, {1,0,1,1,1,0,1,1,0,1}, {1,1,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1} }; int main() { Labyrinth(1, 1, M, N); for (int i = 0; i < M + 2; i++) //打印迷宫路径 { for (int j = 0; j < N + 2; j++) { if (a_maze[i][j] == 4) cout << " "; else cout << a_maze[i][j] << " "; } cout << endl; } return 0; } void exploreWay(int x, int y, stack<unit>& path, unit& a_unit) { if (a_maze[x][y] == 0) //该方向路可以走 { a_unit.x = x; a_unit.y = y; a_maze[x][y] = 2; path.push(a_unit); //新路径入栈 } else //改变方向,准备下一次探路 path.top().next_direction++; } void Labyrinth(int xi, int yi, int xe, int ye) //探查下一个可走的路径 { stack<unit> path; unit a_unit; exploreWay(xi, yi, path, a_unit); while (!path.empty()) { if (path.top().x == xe && path.top().y == ye) break; switch (path.top().next_direction) { case 1: //向东探路 exploreWay(path.top().x + 1, path.top().y, path, a_unit); break; case 2: //向南探路 exploreWay(path.top().x, path.top().y + 1, path, a_unit); break; case 3: //向西探路 exploreWay(path.top().x - 1, path.top().y, path, a_unit); break; case 4: //向北探路 exploreWay(path.top().x, path.top().y - 1, path, a_unit); break; default: //走到死路 a_maze[path.top().x][path.top().y] = 9; //标记为死路 path.pop(); //栈顶退栈 } } while (!path.empty()) //为了打印路径,给迷宫挖空 { a_maze[path.top().x][path.top().y] = 4; path.pop(); } }

运行效果#

  • 虽然这段代码是以深度优先搜索为基础写出来的,但是这并不完整,因为理论上深度优先搜索是可以找到所有路径的。解决方案是找到终点之后仍然走回头路,在有岔路的地方再次进行试探,直到没有岔路可走为止。此处只是为了展示栈结构的应用,深度优先搜索并不是我们当前要谈的问题,因此我简化的操作,并且将路径挖空,帮助我们更好地理解。

迷宫寻路(广度优先)#

应用解析#

在这里我们想利用广度优先的思想来实现,本算法的思想是从 (xi,yi) 开始,利用队列的特点,一层一层扩大搜索的直径,把可走的点都导入到队列中,直到搜索到终点。

不过我这里主要是为了展示队列的应用,因此对于广度优先我在这里不过多阐述,可以自行查阅相关资料理解。这里需要强调的是,由于我们需要得到完整的路径,也就是说搜索过的路径不能够真出队列,以便于我们得到答案,因此就不能使用 STL 库的 queue 容器来实现,自建队列的话就要使用顺序队列来描述。不过我认为此处最适合的是 STL 库的 vector 容器,我们只需要定义两个游标来描述 vector 对象的队列头和尾的位置,就可以使用 vector 的方法来实现插入等操作,我们在解决银行排队问题的时候也是这么做的。

代码实现#

Copy Highlighter-hljs
#include<iostream> #include<vector> using namespace std; #define M 8 #define N 8 typedef struct { int x; int y; //路径的坐标 int pre; //表示该路径前驱的游标 } unit; int exploreWay(int x, int y, vector<unit>& path, unit& a_unit, int front, int& rear); //添加单个路径 void Labyrinth(int xi, int yi, int xe, int ye, vector<unit>& min_path); //路径搜索函数 int a_maze[M + 2][N + 2] = { {1,1,1,1,1,1,1,1,1,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,1,0,0,0,1,0,1}, {1,0,0,0,0,1,1,0,0,1}, {1,0,1,1,1,0,0,0,0,1}, {1,0,0,0,1,0,0,0,0,1}, {1,0,1,0,0,0,1,0,0,1}, {1,0,1,1,1,0,1,1,0,1}, {1,1,0,0,0,0,0,0,0,1}, {1,1,1,1,1,1,1,1,1,1} }; int main() { vector<unit> min_path; //存储最短路径 Labyrinth(1,1, M, N,min_path); cout << "最短路径为:" << endl; for (int i = min_path.size() - 1; i >= 0; i--) { cout << "(" << min_path[i].x << " , " << min_path[i].y << ") "; if ((min_path.size() - i) % 5 == 0) cout << endl; } return 0; } int exploreWay(int x, int y, vector<unit>& path, unit& a_unit, int front, int& rear) { if (a_maze[x][y] == 0) //若路径可走 { a_unit.x = x; a_unit.y = y; a_unit.pre = front; path.push_back(a_unit); //添加路径 rear++; //移动尾指针 a_maze[x][y] = 2; return 1; } return 0; } void Labyrinth(int xi, int yi, int xe, int ye, vector<unit>& min_path) //搜索路径为:(xi,yi)->(xe,ye) { vector<unit> path; //存储所有可走路径 unit a_unit; int front, rear = -1; front = rear; exploreWay(xi, yi, path, a_unit, front, rear); //添加起点路径,同时初始化头尾指针 while (rear != front) { front++; //判断路径东侧结点是否添加 if (exploreWay(path[front].x + 1, path[front].y, path, a_unit, front, rear) == 1 && a_unit.x == xe && a_unit.y == ye) break; //判断路径南侧结点是否添加 if (exploreWay(path[front].x, path[front].y + 1, path, a_unit, front, rear) == 1 && a_unit.x == xe && a_unit.y == ye) break; //判断路径西侧结点是否添加 if (exploreWay(path[front].x - 1, path[front].y, path, a_unit, front, rear) == 1 && a_unit.x == xe && a_unit.y == ye) break; //判断路径北侧结点是否添加 if (exploreWay(path[front].x, path[front].y - 1, path, a_unit, front, rear) == 1 && a_unit.x == xe && a_unit.y == ye) break; } while (rear != -1) //将搜索到的最短路径移动到 min_path { min_path.push_back(path[rear]); rear = path[rear].pre; } }

运行效果#

参考资料#

《大话数据结构》—— 程杰 著,清华大学出版社
《数据结构教程》—— 李春葆 主编,清华大学出版社
《数据结构(C语言版|第二版)》—— 严蔚敏 李冬梅 吴伟民 编著,人民邮电出版社

posted @   乌漆WhiteMoon  阅读(503)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示
CONTENTS