棧是一種特殊的算法,由於他的特性是“後進先出”,使得它特別適合用來求解迷宮!
我們以下面圖片的小迷宮爲例:
迷宮中的小人呆在迷宮的入口位置,他需要找到迷宮的出口,進而走去迷宮。迷宮對應的二維數組也在上圖中標明出來了。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;
}
運行截圖: