一、题目分析
迷宫问题本质上本质上是对数组的一个遍历,向上下左右四个方向进行遍历。当上下左右四个方向都无法行动时,将路径进行回溯,找到另一个可以行动的方向,直到找到出口。
显然,在遍历的过程中会有很多的问题:
1、当向前运动时,对上下左右四个方向进行遍历就会找到走过的路径,所以需要对走过的路径进行一个标记。
2、也会出现数组越界的问题,需要对下标进行控制。
3、最后是路径存储的问题。如何将走过的路径进行保存,并且如果路不通的时候,如何将路径删除,保存新的路径。显然栈是一个很好的方式来保存路径。按照后进先出的原则,可以将路径倒退删除,进行回溯。
4、既然选择了栈来保存数据,那么打印的时候又会遇到新的问题。我们需要打印从开始到结束的路径,然而栈的特性是后进先出,打印时便会将路径倒过来打印。所以需要另一个栈,来将存储路径的数据倒一遍,再将新栈打印。
显然C语言对这个问题的实现是有些麻烦的,我们需要自己写一个栈出来,再解决剩下的问题。
二、代码分析
1、主函数
int N = 0, M = 0;
scanf("%d%d", &N, &M);
int** maze=(int**)malloc(sizeof(int*) * N);//malloc一个二维数组
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(sizeof(int) * M);
}
for (int i = 0; i < N; i++)//数据输入
{
for (int j = 0; j < M; j++)
scanf("%d", &maze[i][j]);
}
malloc一个二维数组maze,用来作为迷宫。0代表路可以走通,1代表无法走通。当然,malloc的空间要在使用完成后free掉,避免内存泄漏。
Stack path;//利用栈保存路径
typedef struct Pos//位置坐标
{
int row;
int col;
}Pos;
建立一个栈path来存储路径,以及一个坐标结构体来表示位置坐标。栈储存的数据类型即为定义的struct Pos。将栈进行初始化,将坐标设为入口坐标{0,0}。在这里将path定义为了全局变量,方便函数的使用。
StackInit(&path);//初始化栈
Pos cur = { 0,0 };//入口位置坐标
if (GetMazePath(maze, N, M, cur))//判断是否能走到出口
{
PrintPath();//打印路径
}
else
{
printf("no path\n");
}
StackDestory(&path);//销毁栈
for (int i = 0; i < N; i++)
free(maze[i]);
free(maze);//释放二维数组
return 0;
GetMazePath函数是用来判断是否能找到路径,将迷宫maze,行N,列M,入口坐标cur传入,返回布尔值。如果返回值为true,将路径进行打印。PrintPath即为路径打印函数。
将用过的栈进行销毁。
2、GetMazePath
当路走不通的时候,要对路径进行回溯,走到一个岔路口,向另一个方向运动。要采用递归的方式进行回溯。
在上图中,当走到(1,0)位置的时候,下方向和右方向都可以前行。
如果先向下运动,当我们走到(3,1)位置时便无法运动了。向下向左走会越界,向右数组值为1,无法运动。当然,上方向我们要进行一个标记,避免向回走的情况。如下图所示:
暂且标记为2,因为只有0才能通,标记为其它数字也无妨。既然在(3,0)无法行动,便要回到其它位置,(2,0)显然无法走通,(1,0)的右边方向是可以的,我们便要回溯到(1,0)这个位置。
并且在运动的时候,代码无法确定此时正在走的路径能能不能到达终点(3,3)处,所以要实时得对路径在栈中进行Push(保存),或者Pop(删除)。
(1)、是否到达终点
StackPush(&path, cur);//保存路径
if (cur.row == N - 1 && cur.col == M - 1)//判断是否到达终端点
return true;
maze[cur.row][cur.col] = 2;//标记走过的路
在每一次运动时,最先要保存一下路径,之后再判断要不要删除路径。
并且要判断是否到达迷宫的终点(数组右下角),如果以及到达终点就没有继续递归的必要了,直接返回true即可。
以及要对走过的路进行一个标记
(2)、判断下一个方向是否通路
Pos next = cur;//下一个位置坐标
next.row -= 1;//向上走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.row += 1;//向下走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.col -= 1;//向左走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.col += 1;//向右走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
StackPop(&path);//删除数据
return false;//返回false表示无路可走
在遍历中有四种情况:向上,向下,向左,向右。
创建一个新的坐标结构体来代表下一个坐标位置。将cur先赋给next
如果向上走,next.row-=1。传入IsPath函数中,判断上方向是否是通路。
如果是通路,就可以继续走下去,直到到达终点或者无路可走。
下方向:next.row+=1;IsPath判断是否能继续走下去。
左方向:next.col-=1;IsPath判断是否能继续走下去。
右方向:next.col+=1;IsPath判断是否能继续走下去。
要注意的一点是:在每一次进入IsPath进行判断时,要将next还原成cur,再改变next。因为在判断其它方向时,next已经被 改变了,next要在cur的基础上进行移动。
通过以上方法,对路径进行递归。当然这是一个抽象的过程,递归就如同一棵大树一样,会产生很多的枝叶,使得这颗大树枝繁叶茂,让人无法理清逻辑。所以我们可以用笔来画一下这棵树。GetMazePath函数用GMP来表示。
以下面数组为例:
依次探查四个方向,第一次进入(1,0),第二次进入(2,0),第三次进入(3,0)
,此时四个方向都不通,GetMazePath函数走到最底部,return false。
向上回溯,并且删掉栈内的路径,找到另一个通路。如上图所示,回溯到(1,0),发现其右侧是通路,向右运动。
最后不断递归,找到终点后结束,整体返回true。
可以看到递归其实在我们的逻辑中实现出来就像一棵树,但是这棵树要从左向右依次遍历。false就返回不向下走,true就继续向下遍历。
单凭想象我们很难去像清楚,将其图像化、具体化更容易明白。
3、IsPath
在GetMazePath中利用这个函数来判断下一步是否是通路,决定是否向下递归。但是写在GetMazePath函数中太过冗长,条理不够清晰。所以单独胡作为一个函数。
int row = next.row;//行坐标
int col = next.col;//列坐标
if(row>=0&&row<N&&col>=0&&col<M&&maze[row][col]==0)//判断是否通路
return true;
else
return false;
首先就是要确保下一步的坐标不能越界,最后要保证maze[row][col]==0,因为走过的路已经被标记为2,所以不会对走过的路判断为true。
通路返回true,不通返回false。
4、PrintPath
在题目分析中可以得知,path栈中的路径虽然被保存了下来,但栈的特性决定了打印的时候路径会倒过来。所以建立一个新的栈rpath来将数据倒一遍,在将rpath中的数据打印即可。
Stack rpath;//建立一个新栈
StackInit(&rpath);//初始化新栈
while (!StackEmpty(&path))
{
StackPush(&rpath, StackTop(&path));//将数据倒入到rpath中
StackPop(&path);//每倒一个数据,在path中删除一个数据
}
while (!StackEmpty(&rpath))
{
Pos pos = StackTop(&rpath);
printf("(%d,%d)\n",pos.row,pos.col);//打印数据
StackPop(&rpath);
}
StackDestory(&rpath);//销毁栈
利用StackEmpty接口来进行循环判断,每向rpath中放入一个数据(StackPush),就要在path中删除一个数据(StackPop)。
接着打印数据即可。
三、完整代码
1、栈
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <string.h>
#define capacity 4
typedef struct Pos
{
int row;
int col;
}Pos;//位置结构体
typedef Pos StackDataType;
typedef struct Stack
{
StackDataType* _a;
int size;//大小
int capa;//容量
}Stack;//栈
void StackInit(Stack* s);//初始化
void StackDestory(Stack* s);//栈销毁
void StackPush(Stack* s, StackDataType x);//入栈
void StackPop(Stack* s);//出栈
int StackSize(Stack* s);//数据个数
int StackEmpty(Stack* s);//栈是否为空
StackDataType StackTop(Stack* s);//栈顶数据
void StackInit(Stack* s)//栈初始化
{
assert(s);
s->_a = (StackDataType*)malloc(sizeof(StackDataType) * capacity);
s->size = 0;
s->capa = capacity;
}
void StackDestory(Stack* s)//栈销毁
{
assert(s);
free(s->_a);
s->_a = NULL;
s->size = 0;
s->capa = 0;
}
void StackPush(Stack* s, StackDataType x)//入栈
{
assert(s);
if (s->capa == s->size)
{
s->capa *= 2;
StackDataType* tmp = (StackDataType*)realloc(s->_a, sizeof(StackDataType) * s->capa);
if (tmp == NULL)
{
perror("StackPush:");
exit(-1);
}
else
{
s->_a = tmp;
}
}
s->_a[s->size] = x;
s->size++;
}
void StackPop(Stack* s)//出栈
{
assert(s);
assert(s->size);
s->size--;
}
int StackSize(Stack* s)//数据个数
{
assert(s);
return s->size;
}
int StackEmpty(Stack* s)//栈是否为空
{
assert(s);
return !(s->size);
}
StackDataType StackTop(Stack* s)//栈顶数据
{
assert(s);
return s->_a[s->size - 1];
}
2、迷宫代码
#include "stack.h"
Stack path;//利用栈保存路径
bool IsPath(int** maze, int N, int M, Pos next)//判断通路函数
{
assert(maze);
int row = next.row;
int col = next.col;
if(row>=0&&row<N&&col>=0&&col<M&&maze[row][col]==0)
return true;
else
return false;
}
bool GetMazePath(int** maze, int N, int M,Pos cur)//获取路径函数
{
StackPush(&path, cur);
if (cur.row == N - 1 && cur.col == M - 1)
return true;
maze[cur.row][cur.col] = 2;
Pos next = cur;
next.row -= 1;//向上走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.row += 1;//向下走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.col -= 1;//向左走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
next = cur;
next.col += 1;//向右走
if (IsPath(maze, N, M, next))
{
if (GetMazePath(maze, N, M, next))
return true;
}
StackPop(&path);
return false;
}
void PrintPath()//打印函数
{
Stack rpath;
StackInit(&rpath);
while (!StackEmpty(&path))
{
StackPush(&rpath, StackTop(&path));
StackPop(&path);
}
while (!StackEmpty(&rpath))
{
Pos pos = StackTop(&rpath);
printf("(%d,%d)\n",pos.row,pos.col);
StackPop(&rpath);
}
StackDestory(&rpath);
}
int main()
{
int N = 0, M = 0;
scanf("%d%d", &N, &M);
int** maze=(int**)malloc(sizeof(int*) * N);
for (int i = 0; i < N; i++)
{
maze[i] = (int*)malloc(sizeof(int) * M);
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < M; j++)
scanf("%d", &maze[i][j]);
}
StackInit(&path);
Pos cur = { 0,0 };
if (GetMazePath(maze, N, M, cur))
{
PrintPath();
}
else
{
printf("no path\n");
}
StackDestory(&path);
for (int i = 0; i < N; i++)
free(maze[i]);
free(maze);
return 0;
}