我们小时候都玩过迷宫,走迷宫可以说是非常有意思了。而在我们大脑里是如何对这个游戏进行思考的呢?其实我们在玩这个游戏的是,大多是一条路走到黑,如果到达出口那么就走出来了,如果是死胡同,那么回到刚才的分叉口,再找一条路再一条路走到黑,以此类推。而我们在实现迷宫求解的时候也是利用这种方法,这种方法又称作回溯法。
我们要实现迷宫求解问题,首先应当是创建一个迷宫。这里我们用一个二维数组当做一个平面,然后用01序列当做墙与路,最终通过走1的路,看是否能够到达出口。
如上图,此时1就是可以走的路,而0就是墙,无法行走。
我们可以创建一个结构体,在结构体内放置地图map。
#define ROW 6
#define COL 6
typedef struct Maze {
int map[ROW][COL];//为了方便我们对地图的控制,这里设置两个全局的行、列
}Maze;
在定义完地图以后,我们考虑的是,如何在地图中走?以及定义规则。
首先我们应该确定入口点的位置。设为entry,而在我们的二维数组中,区别元素的唯一法则就是下标与内容,这里不考虑内容,下标应该是最理想的,所以我们应该再定义一个结构体来存放下标。
typedef struct Point{
int row;//行
int col;//列
}Point;
这样我们在定义入口点,以及接下来每一个走的点都可以用这个Point来处理操作。
接下来就是如何走的问题了。
我们这里采用的是从出口点开始,以顺时针为方向,依次判断入口点上、右、下、左是否能够落脚,而落脚的判断就是这四个点内的元素值是否为1,如果为1,那么证明可以落脚,然后对落脚点进行标记并入栈,落脚以后利用递归对落脚点四周继续判断,以此类推,直至走到出口,或者是无路可走,出栈并返回。然后再判断入口点右、下、左。
所以此时一旦落脚了(1,1)这个点,那么就应该对其进行标记及入栈,接下来应该判断这个点是否为出口,显然不是,那么接着以这个点为主,判断它的四周,以此类推。接下来我们实现。
void MazeInit(Maze* maze)//初始化地图
{
if(maze == NULL) {
return;
}
int map[ROW][COL] = {
{0,1,0,0,0,0},
{0,1,1,1,0,0},
{0,1,0,1,1,0},
{1,1,0,0,1,0},
{0,0,0,0,0,0},
{0,0,0,0,0,0}
};
size_t i = 0;
size_t j = 0;
for(; i < ROW; ++i) {
for(j = 0; j < COL; ++j) {
maze->map[i][j] = map[i][j];
}
}
return;
}
void MazeMapPrint(Maze* maze)//打印地图
{
if(maze == NULL) {
return;
}
printf("\n");
size_t i = 0;
size_t j = 0;
for(; i < ROW; ++i) {
for(j = 0; j < COL; ++j) {
printf("%2d ",maze->map[i][j]);
}
printf("\n");
}
}
int CanStay(Maze* maze, Point cur)//是否能够落脚
{
if(maze == NULL) {
return 0;
}
if(cur.col < 0 || cur.col >= COL || \
cur.row < 0 || cur.row >= ROW) {
return 0;
}
if(maze->map[cur.row][cur.col] == 1) {
return 1;
}
return 0;
}
void MarkStay(Maze* maze, Point cur)//标记落脚点
{
if(maze == NULL) {
return;
}
maze->map[cur.row][cur.col] = 2;
}
int isExit(Maze* maze, Point cur, Point entry)//判断是否是出口点
{
if(maze == NULL) {
return 0;
}
if((cur.row == 0 || cur.row == ROW - 1 || \
cur.col == 0 || cur.col == COL - 1) && \
(cur.col != entry.col || cur.row != entry.row)) {
return 1;
}
return 0;
}
int _GetPathMaze(Maze* maze, Point cur, Point entry)
{
printf("(%d,%d)\n",cur.row, cur.col);
if(maze == NULL) {
return 0;
}
//判断是否能落脚
if(!CanStay(maze, cur)) {
return 0;
}
//标记落脚点
MarkStay(maze, cur);
//判断是否是出口点
if(isExit(maze, cur, entry)) {
printf("找到一条路径\n");
return 1;
}
//如果不是出口点那么就判断它的四周
//预定顺序为上、右、下、左
Point up = cur;
up.row -= 1;
_GetPathMaze(maze, up, entry);
Point right = cur;
right.col += 1;
_GetPathMaze(maze, right, entry);
Point down = cur;
down.row += 1;
_GetPathMaze(maze, down, entry);
Point left = cur;
left.col -= 1;
_GetPathMaze(maze, left, entry);
return 1;
}
void GetPathMaze(Maze* maze,Point entry)//查找路径
{
if(maze == NULL) {
return;
}
entry.row = 0;
entry.col = 1;
_GetPathMaze(maze, entry, entry);
}
我们发现,上面的代码并没有入栈用任何的入栈出栈操作,但是我们的思路里面却说入栈出栈,这是为什么呢?其实这里我们是利用了系统给予我们的栈,函数调用时每次调用函数都会形成自己的栈,函数返回后,栈销毁,数据释放。所以我们不必维护栈,就可以实现这个目的。
接下来,我们利用自己的栈来实现一下这一系列操作。
int _GetPathMazeByMyStack(Maze* maze, Point entry)
{
if(maze == NULL) {
return 0;
}
SeqStack stack;
SeqStackInit(&stack);
//首先判断是否能够落脚
if(!(CanStay(maze, entry))) {
return 0;
}
//如果能够落脚,那么这个时候就标记落脚点,并且入栈
MarkStay(maze, entry);
SeqStackPush(&stack, entry);
//判断是否是出口,如果是出口就进行打印并标记
//如果不是出口点,此时应该判断其四周的点,是否能够落脚
//如果可以落脚就标记并入栈依次循环
Point cur;
while(SeqStackGetFront(&stack, &cur)) {
if(isExit(maze, cur, entry)) {
MarkStay(maze, cur);
printf("找到一条路径\n");
SeqStackPop(&stack);
continue;
}
Point up = cur;
up.row -= 1;
if(CanStay(maze, up)) {
MarkStay(maze, up);
SeqStackPush(&stack, up);
continue;
}
Point right = cur;
right.col += 1;
if(CanStay(maze, right)) {
MarkStay(maze, right);
SeqStackPush(&stack, right);
continue;
}
Point down = cur;
down.row += 1;
if(CanStay(maze, down)) {
MarkStay(maze, down);
SeqStackPush(&stack, down);
continue;
}
Point left = cur;
left.col -= 1;
if(CanStay(maze, left)) {
MarkStay(maze, left);
SeqStackPush(&stack, left);
continue;
}
SeqStackPop(&stack);
}
return 1;
}
void GetPathMazeByMyStack(Maze* maze, Point entry)
{
if(maze == NULL) {
return;
}
entry.row = 0;
entry.col = 1;
_GetPathMazeByMyStack(maze, entry);
return;
}
这里我们利用自己的栈,之前写过的栈操作这里直接使用,不再多说。
我们每次判断能够落脚以后首先先进行入栈操作,而接着判断是否是出口点,如果不是那么进入循环,取栈顶元素判断它的四周是否可以落脚以及是否是出口点,可以落脚就再次入栈并直接continue进行下次循环,再去栈顶元素,比较,以此类推。直至循环结束。
欢迎大家共同讨论,如有错误及时联系作者指出,并改正。谢谢大家!