调试环境:vs2015+win10
众所周知,栈是一个非常常见且有用的数据结构。
这里讲解一下利用栈来实现迷宫问题。
使用递归实现
假设有一迷宫
其中:1代表墙;0代表路径
为简化编程,假设左边界0处为迷宫入口,下边界0处为迷宫出口库。
分析问题:
- 创建一个结构体,表示在迷宫的坐标
- 从文件中读取迷宫
- 获取迷宫路径,需要判断是否可走,将走过的地方标记为2
代码实现:
#include<assert.h>
#include<stack>
#include<iomanip>
#include<iostream>
using namespace std;
struct Pos {
int _row; //行
int _col; //列
Pos& operator+(Pos& pos) { //重载加法运算
_row += pos._row;
_col += pos._col;
return *this;
}
};
//获取地图
void GetMaze(int* maze) {//为什么不传二维数组,因为二维数组在传参时需要声明个数,这样传参简单实用
FILE* fp = fopen("MazeMap1.txt", "r"); //读取地图文件
assert(fp); // 断言
size_t i = 0;
char c = 0;
while ((c = fgetc(fp)) != EOF) { //将从文件读取的一个字符赋值给c,并判断是否是文件尾
if (c == '1' || c == '0') { //c如果是墙或是路
*(maze + i) = c - '0'; //满足就将该字符存入数组
++i;
}
}
}
//打印地图
void PrintMaze(int* maze, size_t N) {
assert(maze);
for (size_t i = 0; i < N; ++i) { //利用二维的方式访问maze
for (size_t j = 0; j < N; ++j) {
cout << maze[i*N + j] << " "; //打印该节点的值
}
cout << endl; //换行
}
}
//判断是否可走
bool CheckIsAccess(int* maze, size_t N, Pos pos) {
assert(maze);
if ((pos._col >= 0) && (pos._col < N) && (pos._row >= 0) && (pos._row < N)) {//判断是否不为边界
if (maze[pos._row*N + pos._col] != 1) { //判断是否是墙
if (maze[pos._row*N + pos._col] == 0) { //判断是否是路
return true; //满足返回true
}
}
}
return false; //不满足返回false
}
//获取地图路径
void GetMazePath(int* maze, size_t N, Pos entry) {
assert(maze);
Pos test[4] = { { -1,0 },{ 0,1 },{ 1,0 },{ 0,-1 } };//声明一个Pos数组,分别表示向上,右,下,左偏移一个单位
Pos cur = entry; //复制当前节点为cur
maze[cur._row*N + cur._col] = 2; //将该节点赋值为2,表示来过
for (int i = 0; i < 4; ++i) { //分别判断该节点的4个方向是否可走
cur = entry; //每次循环时,回复到初始节点
cur = cur + test[i]; //加上偏移量
if (CheckIsAccess(maze, N, cur)) { //判断该节点的该方向是否可走
GetMazePath(maze, N, cur); //满足则进行递归调用,则可以遍历完所有的路径
}
}
}
//测试函数
void TestMaze() {
int* maze = new int[100]; //利用指针声明一个一维数组,方便传参
Pos entry = { 2,0 }; //设置迷宫入口点
GetMaze(maze); //获取地图内容
PrintMaze(maze, 10); //打印迷宫,此次为初始化时的迷宫
GetMazePath(maze, 10, path, entry);//获取迷宫路径
PrintMaze(maze, 10); //打印迷宫,此为遍历完所有路的迷宫地图
}
//主函数
int main(){
TestMaze(); //调用迷宫测试函数
return 0;
}
运行结果:
使用栈实现迷宫
递归调用思路简单,编写起来也很容易,接下来我们将递归改成非递归,就需要用到栈。
同样是上面的那个地图,采用STL里面的栈,实现迷宫
思路与上面大体相同
使用上面的代码,则只需要修改GetMazePath()函数和TestMaze()函数
代码实现:
//获取地图路径
void GetMazePath(int* maze, size_t N, stack<Pos>& path, Pos entry) {
assert(maze);
Pos test[4] = { { -1,0 },{ 0,1 },{ 1,0 },{ 0,-1 } };//上,右,下,左
Pos cur = entry; //赋值当前节点为cur
path.push(cur); //将该节点压入栈中
maze[cur._row*N + cur._col] = 2; //将该节点赋值为2,标记为走过
while (!path.empty()) { //循环条件为栈是否为空
int i = 0;
for (i = 0; i < 4; ++i) {
cur = path.top(); //每次循环时,将cur的值复位为栈顶元素
cur = cur + test[i]; //cur改为新坐标
if (CheckIsAccess(maze, N, cur)) { //判断新坐标是否可走
maze[cur._row*N + cur._col] = 2; //如果可走,将该节点赋值为2
path.push(cur); //并将该节点压入栈中
break; //如果可以通则break,如果此路最终不能达到终点,返回时在判断该坐标点的四周是否可走,即采用深度优先的思想走迷宫
}
}
if (i == 4) { //如果i为4,即说明该坐标的四周都无可走路,则弹栈
path.pop();
}
}
}
//测试函数
void TestMaze() {
stack<Pos> path; //声明一个路径栈
int* maze = new int[100]; //声明一个数组
Pos entry = { 2,0 }; //设置初始入口点
GetMaze(maze); //获取地图
cout << "初始化地图" << endl;
PrintMaze(maze, 10); //打印初始化地图
path.push(entry); //将入口压栈
maze[entry._row * 10 + entry._col] = 2; //将入口赋值为2,表示来过
GetMazePath(maze, 10, path, entry); //获取路径
PrintMaze(maze, 10); //打印迷宫地图
}
运行结果:
使用栈查找迷宫最短路
上面讲解的方法只是遍历了所有路,如果需要返回是否找到通路,可以使用栈来保存路径所经过的坐标
当迷宫具有多条通路时,就需要返回最短路径,所以我们的地图也升级成为了下图
如图所示,在迷宫中有一条回路,即有多条路径可以成为通路
如果需要找到最短路径,不仅需要保存通路的路径,还要保证是最短的,这就需要使用两个栈,一个保存当前通路的路径,另一个保存最短路径
思路也有一点变化
- 获取地图
- 以起点开始,标记为2,此后每走一步,标记自加,存进数组
- 判断是否为最短路
代码上还是只需要改动两个关键的函数即可
代码实现:
stack<Pos> check; //声明一个全局栈,用来保存最短路径
//打印地图
void PrintMaze(int* maze, size_t N) {
assert(maze);
for (size_t i = 0; i < N; ++i) {
for (size_t j = 0; j < N; ++j) {
cout << setw(3) << maze[i*N + j]<<" "; //使用setw()函数,使输出方便观察
}
cout << endl;
}
}
//判断是否可走
bool CheckIsAccess(int* maze, size_t N, Pos pos,stack<Pos> path) {
assert(maze);
if ((pos._col >= 0) && (pos._col < N) && (pos._row >= 0) && (pos._row < N)) { //判断是否越界
if (maze[pos._row*N + pos._col] != 1) { //判断是否为墙
if (maze[pos._row*N + pos._col] == 0) { //判断是否为未走过的路
return true;
}
if (( maze[pos._row*N + pos._col] - maze[path.top()._row*N + path.top()._col])>1) {
//判断该节点与栈顶元素的差值是否大于1,如果为真,即说明该节点曾走过
return true;
}
}
}
else {
if(!(pos._row==2 && pos._col==-1))
//如果不是入口点的左节点,将path的元素赋给check
check = path;
}
return false; //如果不满足上述条件,即表明该节点四周都不能走
}
//获取地图路径
void GetMazePath(int* maze, size_t N, stack<Pos>& path, Pos entry,int js) {
assert(maze);
Pos test[4] = { {-1,0},{0,1},{1,0},{0,-1} };//上,右,下,左
Pos cur = entry;
path.push(cur); //压栈
maze[cur._row*N + cur._col] = js; //当前节点的值
for (int i = 0; i < 4; ++i) {
cur = entry;
cur = cur + test[i];
if (CheckIsAccess(maze, N, cur,path)) { //判断是否可以走
GetMazePath(maze, N, path, cur, js + 1); //每次递归调用时js+1
}
}
path.pop(); //如果本节点的4个方向都探测完后,将此节点弹栈
}
//测试函数
void TestMaze() {
stack<Pos> path;
int* maze= new int[100];
Pos entry = { 2,0 };
GetMaze(maze);
PrintMaze(maze, 10);
path.push(entry);
GetMazePath(maze, 10, path, entry, 2); //初始的时候js赋值为2
PrintMaze(maze, 10);
path.pop(); //最后将迷宫入口点弹栈
}
运行结果:
如运行结果所示,数字按照每条路依次递增,简单明了。
如果想知道具体路径可以查看check里面的值
这里就简单的实现了一下迷宫问题。