栈是一种特殊的算法,由于他的特性是“后进先出”,使得它特别适合用来求解迷宫!
我们以下面图片的小迷宫为例:
迷宫中的小人呆在迷宫的入口位置,他需要找到迷宫的出口,进而走去迷宫。迷宫对应的二维数组也在上图中标明出来了。0表示墙,1表示可以走的路径。
找迷宫通路需要使用回溯法,找迷宫通路是对回溯法的一个很好的应用,实现回溯的过程用到数据结构—栈!
回溯法:对一个包括有很多个结点,每个结点有若干个搜索分支的问题,把原问题分解为若干个子问题求解的算法;当搜索到某个结点发现无法再继续搜索下去时,就让搜索过程回溯(回退)到该节点的前一个结点,继续搜索该节点外的其他尚未搜索的分支;如果发现该结点无法再搜索下去,就让搜索过程回溯到这个结点的前一结点继续这样的搜索过程;这样的搜索过程一直进行到搜索到问题的解或者搜索完了全部可搜索分支没有解存在为止。
也即是说:从小人的位置开始,尝试一组固定的走法,例如:先往左边走,当左边右路走的时候,往左边走一步;然后再往左边走;当左边没路做了,在尝试往上走,上面有路走的话,往上走一步;然后在尝试往左边走…
就这样,没有一步,又重新尝试按照固定步伐走:左 上 右 下(当然可以随意)
当走到一个地方,他的三个方向都走不通的时候,那么就得退回来一步,再试试其他没有走过的方向!
这就是回溯法!
当然,我们没走一步,都得做一下标记,使得我们不会再走相同的路,而迷路。
代码中,我就使用数字自增的方式做标记。例如,当前迷宫入口的位置,也就是小人所在的位置,首先标记2,当他每走一步就在其前一步的基础上加一作为标记。
完整代码实现:
maseStack.h
#pragma once
#include <cstddef>
#define MAX 128
typedef struct _mazeCoord {
int x;
int y;
}Coord;
// 栈
typedef struct _mazeStack {
Coord *top; // 栈头
Coord *base; // 栈尾
}Stack;
bool initStack(Stack &stack); // 初始化栈
bool estimateStackEmpty(Stack &stack); // 栈是否为空
bool estimateStackFull(Stack &stack); // 栈是否已满
bool accessStack(Stack &stack, Coord &coord); // 入栈
bool popStack(Stack &stack, Coord &coord); // 出栈
bool gainStack(Stack &stack, Coord &coord); // 获取栈顶的元素
bool initStack(Stack &stack) {
stack.base = new Coord[MAX]; // 分配内存
if (!stack.base) {
return false;
}
stack.top = stack.base; // 栈顶栈底指向同一个位置
return true;
}
bool estimateStackEmpty(Stack &stack) {
if (stack.top == stack.base) {
return true;
}
return false;
}
bool estimateStackFull(Stack &stack) {
if ((stack.top - stack.base) == MAX) {
return true;
}
return false;
}
bool accessStack(Stack &stack, Coord &coord) {
if (estimateStackFull(stack)) {
return false;
}
*stack.top = coord;
stack.top++;
return true;
}
bool popStack(Stack &stack, Coord &coord) {
if (estimateStackEmpty(stack)) {
return false;
}
stack.top--;
coord = *stack.top;
return true;
}
bool gainStack(Stack &stack, Coord &coord) {
if (estimateStackEmpty(stack)) {
return false;
}
coord = *(stack.top - 1);
return true;
}
bool clearStack(Stack &stack) {
if (!stack.base) {
return false;
}
delete stack.base;
stack.top = NULL;
stack.base = NULL;
return false;
}
maze.cpp
#include <iostream>
#include <Windows.h>
#include "mazeStack.h"
using namespace std;
#define ROW 6
#define LINE 6
// 迷宫路径显示地图
typedef struct _Mase {
int map[ROW][LINE];
}Mase;
void initMap(Mase *m, int *map); // 初始化迷宫地图
void PrintMap(Mase *m); // 打印迷宫地图
// 判断迷宫入口是否有效
bool isMazeEntranceValid(Mase *m, Coord *cur); // 参数一:迷宫地图;参数二:入口坐标
// 判断下一个位置是否合法
bool isNextValid(Mase *m, Coord *cur, Coord *next); // 参数二:当前位置坐标;参数三:当前位置的下一个位置的坐标
// 判断当前位置是不是迷宫出口
bool isCoordExit(Coord *cur, Coord *enter); // 参数二:当前位置坐标;参数三:入口位置坐标
// 通过迷宫
bool passMaze(Mase *m, Stack *stack, Coord *enter); // 参数二:堆;参数三:入口位置坐标
void initMap(Mase *m, int *map) {
if (!m || !map) {
return;
}
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < LINE; j++) {
m->map[i][j] = *(map + LINE * i + j);
}
}
}
void PrintMap(Mase *m) {
if (!m) {
return;
}
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < LINE; j++) {
cout << m->map[i][j] << " ";
}
cout << endl;
}
}
bool isMazeEntranceValid(Mase *m, Coord *cur) {
if (!m || !cur) {
return false;
}
if ((cur->x == 0 || cur->x == ROW - 1) || (cur->y == 0 || cur->y == LINE - 1) && // 是否在边上
(m->map[cur->x][cur->y] == 1)) { // 数值是否合法
return true;
}
return false;
}
bool isNextValid(Mase *m, Coord *cur, Coord *next) {
if (!m || !cur || !next) {
return false;
}
if (((cur->x == next->x) && (cur->y + 1 == next->y || cur->y - 1 == next->y)) || // 判断是否再同一行
((cur->y == next->y) && (cur->x + 1 == next->x || cur->x - 1 == next->x))) { // 判断是否再同一列
// 判断在数组中的合法性
if (((next->x >= 0 && next->x < ROW) && // 列的合法性
(next->y >= 0 && next->y < LINE)) && // 行的合法性
(m->map[next->x][next->y] == 1)) { // 数值符合性
return true;
}
}
return false;
}
bool isCoordExit(Coord *cur, Coord *enter) {
if (!enter || !cur) {
return false;
}
//这里首先得保证该节点不是入口点,其次只要它处在迷宫的边界即可
if ((cur->x != enter->x || cur->y != enter->y) && // 出口不能等于入口
((cur->x == 0 || cur->x == ROW - 1) || // 是否在边行上
(cur->y == 0 || cur->y == LINE - 1))) { // 是否在边列上
return true;
}
return false;
}
bool passMaze(Mase *m, Stack *stack, Coord *enter) {
if (!m || !stack || !enter) {
return false;
}
if (!isMazeEntranceValid(m, enter)) { // 判断迷宫入口是否合法
return false;
}
Coord cur = *enter;
Coord next;
accessStack(*stack, cur); // 迷宫入口入栈
m->map[cur.x][cur.y] = 2; // 将当前入口值改为2
while (!estimateStackEmpty(*stack)) { // 如果栈中还有元素,那么执行循环
gainStack(*stack, cur); // 获取栈顶的元素
if (isCoordExit(&cur, enter)) { // 判断当前位置是否是迷宫出口
return true;
}
// 向左走一步
next = cur;
next.y = next.y - 1; // 向左走一步
// 判断左走一步在迷宫地图上是否合法
if (isNextValid(m, &cur, &next)) {
accessStack(*stack, next); // 入栈
m->map[next.x][next.y] = m->map[cur.x][cur.y] + 1; // 迷宫左走一步的地图值被复制其前一个地图值加一
continue;
}
// 向上走一步
next = cur;
next.x = next.x - 1; // 向上走一步
// 判断上走一步在迷宫地图上是否合法
if (isNextValid(m, &cur, &next)) {
accessStack(*stack, next);
m->map[next.x][next.y] = m->map[cur.x][cur.y] + 1; // 迷宫上走一步的地图值被复制其前一个地图值加一
continue;
}
// 向右走一步
next = cur;
next.y = next.y + 1; // 向右走一步
// 判断右走一步在迷宫地图上是否合法
if (isNextValid(m, &cur, &next)) {
accessStack(*stack, next);
m->map[next.x][next.y] = m->map[cur.x][cur.y] + 1; // 迷宫右走一步的地图值被复制其前一个地图值加一
continue;
}
// 向下走一步
next = cur;
next.x = next.x + 1; // 向下走一步
// 判断下走一步在迷宫地图上是否合法
if (isNextValid(m, &cur, &next)) {
accessStack(*stack, next);
m->map[next.x][next.y] = m->map[cur.x][cur.y] + 1; // 迷宫下走一步的地图值被复制其前一个地图值加一
continue;
}
//走到这里说明当前节点的四个方向都走不通,进行回溯,看前一个节点未被遍历的方向是否还能走通
Coord tmp;
popStack(*stack, tmp); // 出栈
}
return false;
}
int main(void) {
int map[ROW][LINE] = { //用二维数组描绘迷宫:1 代表通路,0 代表墙
0,0,1,0,0,0,
0,0,1,1,1,0,
0,0,1,0,0,0,
0,1,1,1,1,0,
0,0,1,0,1,0,
0,0,0,0,1,0
};
Mase m; // 迷宫地图
Coord enter; // 迷宫入口
enter.x = 0;
enter.y = 2;
initMap(&m, *map); // 初始化迷宫地图
PrintMap(&m); // 打印迷宫地图
Stack stack; // 定义栈
initStack(stack); // 初始化栈
//使用栈和回溯法解开迷宫
bool ret = passMaze(&m, &stack, &enter);
if (ret) {
cout << endl << "找到迷宫出口啦!" << endl;
} else {
cout << endl << "迷宫没有出口!" << endl;
}
PrintMap(&m); // 打印迷宫地图
clearStack(stack); // 释放栈的内存
system("pause");
return 0;
}
运行截图: