迷宫问题是栈这一块很经典的问题。
迷宫大致可分为三种,简单迷宫、多通路迷宫:通路间不带环、多通路迷宫:通路间带环,其中带环多通路迷宫是最复杂的,解决它,要把栈与递归结合起来,下来我们来一个一个分析吧,先从简单迷宫开始。
简单迷宫
要解决这个问题并不难,我们只要从入口进入,当然要先检测这个入口是不是合法并且能不能走的通,如果走的通,把当前这一步的位置入栈,并且把它标记为2,然后继续向当前这一步的其他三个方向走,如果其他方向都走不了,说明上一步走错了,我们要回退,把此时的栈顶元素出栈,并把这一步标为3。
看了上边两幅图,我相信大家因该很清楚它的过程了吧,下边我们就来看看代码吧。
Maze.h //头文件
#ifndef __MAZE_H__
#define __MAZE_H__
#include<assert.h>
#include<stdio.h>
#define MaxSize 20
#define Row 6
#define Col 6
typedef struct Position
{
int _x;
int _y;
}Position;
typedef Position SDataType;
typedef struct Stack
{
int top;
SDataType _arry[MaxSize];
}Stack;
typedef struct Maze
{
int _map[Row][Col];
}Maze;
void PassMaze(Maze* m, Position enter);
void InitMaze(Maze* m, int map[Row][Col]);
void PrintMaze(Maze* m);
int IsValidEnter(Maze* m, Position enter);//是否为入口
void StackPush(Stack* ps, SDataType data);
void StackInit(Stack* ps);
void StackPop(Stack* ps);
int IsExit(Position pos, Position enter);//是否为出口
int IsPass(Maze* m, Position pos);
int StackEmpty(Stack* ps);
SDataType StackTop(Stack* ps);//返回栈顶元素
#endif //__MAZE_H__
Maze.c //源文件
#include"Maze.h"
void InitMaze(Maze* m, int map[Row][Col])
{
assert(m != NULL);
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
m->_map[i][j] = map[i][j];
}
}
}
void PrintMaze(Maze* m)
{
assert(m != NULL);
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
printf("%d ", m->_map[i][j]);
}
printf("\n");
}
printf("\n");
}
int IsValidEnter(Maze* m, Position enter)
{
assert(m != NULL);
if ((enter._x >= 0 || enter._x < Row) && (enter._y >= 0 || enter._y < Col))
{
return 1;
}
return 0;
}
void StackPush(Stack* ps, SDataType data)
{
assert(ps != NULL);
if (ps->top == MaxSize)
return;
else
{
ps->_arry[ps->top] = data;
ps->top++;
}
}
void StackInit(Stack* ps)
{
assert(ps != NULL);
ps->top = 0;
}
void StackPop(Stack* ps)
{
assert(ps != NULL);
if (ps->top)
ps->top--;
}
int IsExit(Position pos, Position enter)
{
if ((pos._x == 0 || pos._y == Row - 1) || (pos._y == 0 || pos._y == Col - 1) && (pos._x !=enter._x || pos._y !=enter._y))
{
return 1;
}
return 0;
}
int IsPass(Maze* m, Position pos)
{
assert(m != NULL);
if (1 == m->_map[pos._x][pos._y])
{
return 1;
}
return 0;
}
int StackEmpty(Stack* ps)
{
assert(ps != NULL);
if (0 == ps->top)
return 1;//栈空
return 0;
}
SDataType StackTop(Stack* ps)
{
assert(ps != NULL);
return ps->_arry[ps->top - 1];
}
void PassMaze(Maze* m, Position enter)
{
assert(m != NULL);
if (!IsValidEnter(m, enter))//如果入口不合法
{
printf("入口不合法!!!\n");
return;
}
else
{
Stack s;
StackInit(&s);
StackPush(&s, enter);
while (!StackEmpty(&s))//当栈不空时
{
Position pos;
Position next;
pos = StackTop(&s);
m->_map[pos._x][pos._y] = 2;//将走过的路标记为2
if (IsExit(pos, enter))//判断是不是出口
return;
else
{
//上
next = pos;
next._x -= 1;
if (IsPass(m, next))//判断是否可以走通,只有为1 时才可以走通
{
StackPush(&s, next);
continue;
}
//左
next = pos;
next._y -= 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
//右
next = pos;
next._y += 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
//下
next = pos;
next._x += 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
m->_map[pos._x][pos._y] = 3;
StackPop(&s);
}
}
}
}
test.c //测试文件
#include"Maze.h"
void test()
{
Maze m;
Position enter;
enter._x = 5;
enter._y = 2;
int map[Row][Col] = { { 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 0 },
{ 0, 0, 1, 0, 1, 1 },
{ 0, 0, 1, 0, 0, 0 } };
InitMaze(&m, map);
PrintMaze(&m);
PassMaze(&m, enter);
PrintMaze(&m);
}
int main()
{
test();
return 0;
}
运行结果正确:
多通路迷宫:通路间不带环
其实解决这个问题也挺简单的,我们只要在找到一个出口后,把出口位置置为0,再让它向回退,就可以找到第二条路径了。
我们也来看看它的代码吧:
Maze.h //头文件
#ifndef __MAZE_H__
#define __MAZE_H__
#include<assert.h>
#include<stdio.h>
#define MaxSize 20
#define Row 6
#define Col 6
typedef struct Position
{
int _x;
int _y;
}Position;
typedef Position SDataType;
typedef struct Stack
{
int top;
SDataType _arry[MaxSize];
}Stack;
typedef struct Maze
{
int _map[Row][Col];
}Maze;
void PassMaze(Maze* m, Position enter);
void InitMaze(Maze* m, int map[Row][Col]);
void PrintMaze(Maze* m);
int IsValidEnter(Maze* m, Position enter);//是否为入口
void StackPush(Stack* ps, SDataType data);
void StackInit(Stack* ps);
void StackPop(Stack* ps);
int IsExit(Position pos, Position enter);//是否为出口
int IsPass(Maze* m, Position pos);
int StackEmpty(Stack* ps);
SDataType StackTop(Stack* ps);//返回栈顶元素
#endif //__MAZE_H__
Maze.c //源文件
#include"Maze.h"
void InitMaze(Maze* m, int map[Row][Col])
{
assert(m != NULL);
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
m->_map[i][j] = map[i][j];
}
}
}
void PrintMaze(Maze* m)
{
assert(m != NULL);
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
printf("%d ", m->_map[i][j]);
}
printf("\n");
}
printf("\n");
}
int IsValidEnter(Maze* m, Position enter)
{
assert(m != NULL);
if ((enter._x >= 0 || enter._x < Row) && (enter._y >= 0 || enter._y < Col))
{
return 1;
}
return 0;
}
void StackPush(Stack* ps, SDataType data)
{
assert(ps != NULL);
if (ps->top == MaxSize)
return;
else
{
ps->_arry[ps->top] = data;
ps->top++;
}
}
void StackInit(Stack* ps)
{
assert(ps != NULL);
ps->top = 0;
}
void StackPop(Stack* ps)
{
assert(ps != NULL);
if (ps->top)
ps->top--;
}
int IsExit(Position pos, Position enter)
{
if ((pos._x == 0 || pos._y == Row - 1) || (pos._y == 0 || pos._y == Col - 1) && (pos._x != enter._x || pos._y != enter._y))
{
return 1;
}
return 0;
}
int IsPass(Maze* m, Position pos)
{
assert(m != NULL);
if (1 == m->_map[pos._x][pos._y])
{
return 1;
}
return 0;
}
int StackEmpty(Stack* ps)
{
assert(ps != NULL);
if (0 == ps->top)
return 1;//栈空
return 0;
}
SDataType StackTop(Stack* ps)
{
assert(ps != NULL);
return ps->_arry[ps->top - 1];
}
void PassMaze(Maze* m, Position enter)
{
assert(m != NULL);
if (!IsValidEnter(m, enter))//如果入口不合法
{
printf("入口不合法!!!\n");
return;
}
else
{
Stack s;
StackInit(&s);
StackPush(&s, enter);
while (!StackEmpty(&s))
{
Position pos;
Position next;
pos = StackTop(&s);//取当前栈顶元素
m->_map[pos._x][pos._y] = 2;//将走过的路标记为2
if (IsExit(pos, enter))
{
m->_map[pos._x][pos._y] = 0;//将出口位置置为0
StackPop(&s);
continue;
}
else
{
//上
next = pos;
next._x -= 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
//左
next = pos;
next._y -= 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
//右
next = pos;
next._y += 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
//下
next = pos;
next._x += 1;
if (IsPass(m, next))
{
StackPush(&s, next);
continue;
}
m->_map[pos._x][pos._y] = 3;//将回退的路标为3
StackPop(&s);
}
}
}
}
test.c //测试文件
#include"Maze.h"
void test()
{
Maze m;
Position enter;
enter._x = 5;
enter._y = 2;
int map[Row][Col] = { { 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 0, 0, 0 },
{ 0, 0, 1, 1, 1, 1 },
{ 0, 0, 1, 0, 0, 0 } };
InitMaze(&m, map);
PrintMaze(&m);
PassMaze(&m, enter);
PrintMaze(&m);
}
int main()
{
test();
return 0;
}
运行结果正确
最后,我们就来看看最复杂的迷宫:多通路迷宫(通路间带环)
在这里我们就不能再让走过的路直接标记为2了,因为如果再按上边那种方法,我们只能找到一条路径,但并不是最短路径,在(4,3)的时候,可以向左走但是走到左边(4,2),(4,2)位置标记为2,它的四边都走不通,只能回退,然后超(4,3)的右走,走的通,标记为2,再向(4,4)的上(不通)、左(不通)、右(通),标记为2,是出口了,出来,虽然出口是找到了,但我们发现这并不是一条最短路径,我们要找到它的最短路径,判断如果是出口,我们就让它出栈,回退,一直找到可以走第二条路为止,我们发现,在(4,1)的右边本来可以走通,找到最短路径,但刚刚我们已经走过它,并吧它标记为2了,所以不能再走了,这样我们就找不到最短路径了,所以像上边两种做法一样,把走过的路标记为2是没有办法解决复杂迷宫的。
那我们可以想另外一种办法来解决这个问题,我们可以把入口点标记为2,让每次把它的下一步标记为它的上一步加1,如果它的下一步为1或者大于它,就可以走的通,到出口让它出栈往回退。
在这次和上次不变的是如果走的通,入栈;如果走错了出栈,回退也是出栈,回退的时候,我们也要判断回退的这一步它的四个方向能否走通,因为在刚刚有可能它的其他三个方向可以走通,但我们先检测了它的上方,发现可以走通,我们就超上走了,其他可以走通的方向并没有走,所以在回退的时候我们要检测,这样就可以找到最短路径了。
我们就先来分析一下这个代码的框架吧。
在代码里我们还有很多细节,我们再来细分一下吧。
在判断是出口的时候,我们要出口先入栈,因为这时只是判断了,还并没有入栈,然后再保存最短路径,这就出现了一个问题,我们什么时候保存最短路径呢?在走迷宫的时候,我们每走一步,我们都把它的坐标保存到栈里了,所以我们保存最短路径的时候,就要比较当前栈里的元素个数与保存最短路径的栈里元素个数,如果size(Pah())
//判断是否为出口,若为出口,保存最短路径,Pop
if (IsExit(m, cur, enter))
{
StackPush(Path, cur);
if (StackSize(Path) < StackSize(shortPath) || StackEmpty(shortPath))
{
SaveshortPath(shortPath, Path);
}
StackPop(Path);
//return;
}
void SaveshortPath(Stack* shortPath, Stack* Path)
{
assert(Path);
assert(shortPath);
int i = 0;
int size = 0;
size = StackSize(Path);
for (i = 0; i < size; i++)
{
shortPath->_arry[i] = Path->_arry[i];
}
shortPath->top = size;
}
判断下一步能否走通(我们在前边也分析了,只要下一步比上一步的值大也可以走通):
int IsPass(Maze* m, Position cur, Position next)
{
assert(m != NULL);
if (m->_map[next._x][next._y] > m->_map[cur._x][cur._y] || m->_map[next._x][next._y] == 1)
return 1;
return 0;
}
下来,我们看一下完整的代码吧:
Maze.h //头文件
我们要定义一个栈的结构体,每次入栈的元素类型是坐标形式的,所以还要定义一个Position的结构体。
#ifndef __MAZE_H__
#define __MAZE_H__
#include<stdio.h>
#include<assert.h>
#define MaxSize 20
#define Row 6
#define Col 6
typedef struct Position
{
int _x;
int _y;
}Position;
typedef Position SDataType;
typedef struct Stack
{
SDataType _arry[MaxSize];
int top;
}Stack;
typedef struct Maze
{
int _map[Row][Col];
}Maze;
void PrintMaze(Maze* m);
void InitMaze(Maze* m, int map[Row][Col]);
void PassMaze(Maze* m, Position enter, Stack* shortPath);
void StackInit(Stack* ps);
void StackPush(Stack* ps, SDataType data);
void StackPop(Stack* ps);
int IsExit(Maze* m, Position pos, Position enter);
int StackEmpty(Stack* ps);
SDataType StackTop(Stack* ps);
int IsValidEnter(Maze* m, Position enter);
int StackSize(Stack* ps);
int IsPass(Maze* m, Position cur, Position next);
void _GetMazeshortPath(Maze* m, Position cur, Position enter, Stack* Path, Stack* shortPath);
void SaveshortPath(Stack* Path, Stack* shortPath);
#endif //__MAZE_H__
Maze.c //源文件
还有很多细节,比如我们在定义一个栈之后我们要把它初始化,不然就会出错。
在void PassMaze(Maze* m, Position enter, Stack* shortPath)函数中,我们只需要把地图,入口,和保存最短路径的栈传进去就行,因为我们左后需要的就是最短路径。
void PassMaze(Maze* m, Position enter, Stack* shortPath)
{
Stack Path;
StackInit(&Path);
assert(m != NULL);
if (!IsValidEnter(m, enter))//如果入口不合法
{
printf("入口不合法!!!\n");
return;
}
else
_GetMazeshortPath(m, enter, enter, &Path, shortPath);
}
在void _GetMazeshortPath(Maze* m, Position cur, Position enter, Stack* Path, Stack* shortPath)函数中,我们还要把最开始的入口点传入,因为我们要判断当前位置是否为出口,判断出口的方法就是在判断是入口的基础上加上不能为入口这一条,我们还要传入Path()这个栈的指针,因为我们要保存当前路径。
void _GetMazeshortPath(Maze* m, Position cur, Position enter, Stack* Path, Stack* shortPath)
{
Position next;
//判断是否为出口,若为出口,保存最短路径,Pop
if (IsExit(m, cur, enter))
{
StackPush(Path, cur);
if (StackSize(Path) < StackSize(shortPath) || StackEmpty(shortPath))
{
SaveshortPath(shortPath, Path);
}
StackPop(Path);
//return;
}
if (StackEmpty(Path))
m->_map[enter._x][enter._y] = 2;
StackPush(Path, cur);
//上
next = cur;
next._x -= 1;
if (IsPass(m, cur, next))
{
m->_map[next._x][next._y] = m->_map[cur._x][cur._y] + 1;//标记
_GetMazeshortPath(m, next, enter, Path, shortPath);
}
//左
next = cur;
next._y -= 1;
if (IsPass(m, cur, next))
{
m->_map[next._x][next._y] = m->_map[cur._x][cur._y] + 1;
_GetMazeshortPath(m, next, enter, Path, shortPath);
}
//右
next = cur;
next._y += 1;
if (IsPass(m, cur, next))
{
m->_map[next._x][next._y] = m->_map[cur._x][cur._y] + 1;
_GetMazeshortPath(m, next, enter, Path, shortPath);
}
//下
next = cur;
next._x += 1;
if (IsPass(m, cur, next))
{
m->_map[next._x][next._y] = m->_map[cur._x][cur._y] + 1;
_GetMazeshortPath(m, next, enter, Path, shortPath);
}
//说明上步走错了
StackPop(Path);
}
test.c //测试文件
#include"Maze.h"
void test()
{
Position enter;
enter._x = 5;
enter._y = 1;
Maze m;
Stack shortPath;
StackInit(&shortPath);
int map[Row][Col] = {
{ 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0 },
{ 0, 1, 0, 1, 0, 0 },
{ 0, 1, 1, 1, 1, 1 },
{ 0, 1, 0, 0, 0, 0 } };
InitMaze(&m, map);
PrintMaze(&m);
PassMaze(&m, enter, &shortPath);
PrintMaze(&m);
}
int main()
{
test();
return 0;
}
运行结果正确: