问题引出
迷宫求解是栈的典型例子,今天我们来讨论的是简单的迷宫求解问题,即
迷宫只有一个出口,迷宫内的路径不带环,不相交
例如:
回溯法
在正式求解之前,我们要知道一个很重要的概念
- 回溯法:对一个包括有很多个结点,每个结点有若干个搜索分支的问题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止
解题思路
使用回溯法可以进行迷宫求解,那么基于回溯法,我们可以得到两种解题思路,一种是通过递归函数,使用系统自带的栈帧,第二种是自己构建一个栈,思路分别如下
方法一(使用递归函数)
- 判定入口点是非能落脚,不能的话说明参数非法,返回
- 当前点是否为出口
- 标记入口点,入栈该点
- 如果不是,按顺时针方向依次取相邻点,并判定他们是否能落脚,如果可以,入栈,进入下一轮循环
- 四个点都不能落脚,return 当前递归,回溯
方法二(手动构建栈)
- 创建一个栈并初始化,这个栈保存走过的路经
- 判定入口点是非能落脚,不能的话说明参数非法,返回
- 标记入口点,入栈该点
- 进入循环,先拿到当前的栈顶元素(该元素一定合法)
- 判定是否为出口,如果是,返回
- 如果不是,按顺时针方向依次取相邻点,并判定他们是否能落脚,如果可以,入栈,进入下一轮循环
- 四个点都不能落脚,出栈当前栈顶元素,回溯
方法实现
这里我们要用到栈的一些操作,对于栈的概念和操纵还不太理解的可以移步
maze.c
// 简单迷宫求解
// 简单迷宫:只有一条出口的迷宫,迷宫内部各路径不交叉,不成环
#define REVSERSE 0
// 进行条件编译,通过将 REVERSE 定义为不同的值,使函数执行递归解法和非递归解法两个部分
#if REVERSE == 1
#include <stdio.h>
#include <stdlib.h>
#else
#include "seqstack.h"
#include <stdio.h>
#endif
// 迷宫的长宽
#define ROW 10 // 行
#define COL 10 // 列
typedef struct Maze {
int map[COL][ROW];
} Maze;
// 创建迷宫
void createMaze(Maze* maze) {
int tmp[COL][ROW] = {
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 1, 1, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 1, 0, 0, 1, 0, 1, 0, 1, 0 },
{ 0, 1, 1, 0, 1, 1, 1, 0, 1, 0 },
{ 0, 0, 1, 0, 0, 0, 0, 0, 1, 0 },
{ 0, 0, 1, 1, 1, 0, 0, 0, 1, 0 },
{ 0, 0, 0, 0, 1, 0, 1, 1, 1, 0 },
{ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 }
};
int i = 0;
for( ; i < COL; i++) {
int j = 0;
for( ; j < ROW; j++) {
maze->map[i][j] = tmp[i][j];
}
}
}
// 打印迷宫
void printMaze(Maze maze) {
int i = 0;
// 装饰性打印
printf(" +");
for( ; i < ROW*2; i++) {
printf("-");
}
printf("-+");
printf("\n");
// 打印开始
for( i = 0; i < COL; i++) {
int j = 0;
printf(" | ");
for( ; j < ROW; j++) {
printf("%d ", maze.map[i][j]);
}
printf("|\n");
}
// 装饰性打印
printf(" +");
for( i = 0; i < ROW*2; i++) {
printf("-");
}
printf("-+\n");
}
// 判断当前点是否能够落脚
int Canstay(Maze* maze, Coordinates pos) {
// 越界
if(pos.row>=0 && pos.row<ROW && pos.col>=0 && pos.col<COL) {
if(maze->map[pos.col][pos.row] == 1) {
return 1;
}
return 0;
}
return 0;
}
// 将当前点进行标记
void Mark(Maze* maze, Coordinates pos) {
maze->map[pos.col][pos.row] = 2;
}
// 是否为出口
int isExit(Maze* maze, Coordinates entry, Coordinates pos) {
if((pos.row==entry.row) && (pos.col==entry.col)) {
return 0;
}
if(pos.row==0 || pos.row==ROW-1 || pos.col==0 || pos.col==COL-1) {
return 1;
}
return 0;
}
// 打印路径
void printPath(Maze maze) {
int i = 0;
for( ; i < COL; i++) {
int j = 0;
for( ; j < ROW; j++) {
if(maze.map[i][j] == 2) {
printf("(%d, %d)\n", i, j );
}
}
}
}
#if REVERSE == 1
// 递归求解迷宫
// 递归函数
void _getMazePath(Maze* maze, Coordinates entry, Coordinates pos) {
// 当前点是否能够落脚
if(!Canstay(maze, pos)) {
return;
}
// 当前点是否为出口
if(isExit(maze, entry, pos) == 1) {
printf("找到出口了\n");
return;
}
// 标记当前点
Mark(maze, pos);
// 上
Coordinates up;
up.col = pos.col-1;
up.row = pos.row;
_getMazePath(maze, entry, up);
// 右
Coordinates right;
right.col = pos.col;
right.row = pos.row+1;
_getMazePath(maze, entry, right);
// 下
Coordinates down;
down.col = pos.col+1;
down.row = pos.row;
_getMazePath(maze, entry, down);
// 左
Coordinates left;
left.col = pos.col;
left.row = pos.row-1;
_getMazePath(maze, entry, left);
}
// 求解迷宫线路
void getMazePath(Maze* maze, Coordinates entry) {
// 当前点不能够落脚
if(!Canstay(maze, entry)) {
return;
}
// 标记当前点已经经过
Mark(maze, entry);
// 开始进行递归
// 上
Coordinates up;
up.col = entry.col-1;
up.row = entry.row;
_getMazePath(maze, entry, up);
// 右
Coordinates right;
right.col = entry.col;
right.row = entry.row+1;
_getMazePath(maze, entry, right);
// 下
Coordinates down;
down.col = entry.col+1;
down.row = entry.row;
_getMazePath(maze, entry, down);
// 左
Coordinates left;
left.col = entry.col;
left.row = entry.row-1;
_getMazePath(maze, entry, left);
return;
}
#else
// 迷宫求解(非递归)
// 创建一个栈并初始化,这个栈保存走过的路经
// 判定入口点是非能落脚,不能的话说明参数非法,返回
// 标记入口点,入栈该点
// 进入循环,先拿到当前的栈顶元素(该元素一定合法)
// 判定是否为出口,如果是,返回
// 如果不是,按顺时针方向依次取相邻点,并判定他们是否能落脚,如果可以,入栈,进入下一轮循环
// 四个点都不能落脚,出栈当前栈顶元素,回溯
// 注意:不能进行多条通路的探索
void getMazePath(Maze* maze, Coordinates entry) {
// 创建一个栈并初始化,这个栈保存走过的路经
SeqStack stack;
SeqStackInit(&stack);
// 判定入口点是非能落脚,不能的话说明参数非法,返回
if(!Canstay(maze, entry)) {
return;
}
// 标记入口点,入栈该点
Mark(maze, entry);
SeqStackPush(&stack, entry);
int ret = 1;
while(ret == 1) {
// 进入循环,先拿到当前的栈顶元素(该元素一定合法)
Coordinates cur;
ret = SeqStackTop(&stack, &cur);
// 判定是否为出口,如果是,返回
if(isExit(maze, entry, cur) == 1) {
printf("找到出口啦![%d, %d]\n", cur.col, cur.row);
return;
}
// 如果不是,按顺时针方向依次取相邻点,并判定他们是否能落脚,如果可以,入栈,进入下一轮循环
// 上
Coordinates up;
up.col = cur.col-1;
up.row = cur.row;
if(Canstay(maze, up)) {
Mark(maze, up);
SeqStackPush(&stack, up);
continue;
}
// 右
Coordinates right;
right.col = cur.col;
right.row = cur.row+1;
if(Canstay(maze, right)) {
Mark(maze, right);
SeqStackPush(&stack, right);
continue;
}
// 下
Coordinates down;
down.col = cur.col+1;
down.row = cur.row;
if(Canstay(maze, down)) {
Mark(maze, down);
SeqStackPush(&stack, down);
continue;
}
// 左
Coordinates left;
left.col = cur.col;
left.row = cur.row-1;
if(Canstay(maze, left)) {
Mark(maze, left);
SeqStackPush(&stack, left);
continue;
}
// 四个点都不能落脚,出栈当前栈顶元素,回溯
SeqStackPop(&stack);
}
}
#endif
int main() {
Maze maze;
Coordinates entry;
entry.col = 0;
entry.row = 1;
// 创建迷宫
createMaze(&maze);
// 打印迷宫
printMaze(maze);
// 求解迷宫线路
getMazePath(&maze, entry);
// 打印路径
printPath(maze);
printMaze(maze);
}