本文適合於對迷宮問題已有初步研究,或閱讀代碼能力較強的人.
因此,如果你對迷宮問題一無所知,請參考其他更詳細的資料.
迷宮問題,是一個對棧(Stack)典型應用的例子之一.
假如,有如下10X10的迷宮(0代表通路,1代表障礙),我們需要用寫程序來找出迷宮的出口.
1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 0 1 1 0 0 0 0 1 0 0 0 1 1 1 1 0 1 1 0 1 0 0 1 1 1 0 1 0 0 1 0 1 1 1 1 0 1 1 1 1 0 0 1 1 1 0 0 0 0 0 0 1 1 1 1 0 1 0 1 1 0 1 1 1 1 0 1 0 1 1 0 1 1 1 1 1 1 1 1 1 0 1 1
那么,我們可以通過兩種方式完成.
方式一:通過利用棧FILO(First In Last Out)的特性
核心代碼
/*
*函數說明:通過棧來進行迷宮求解
*參數說明:
* Maze:迷宮地圖數組
* sz:迷宮大小
* entry:迷宮入口點
* path:用於尋找迷宮出口的棧
*返回值:找到出口返回true,沒找到返回false.
*/
bool FindMazePath(int *Maze,size_t sz,Pos &entry,stack<Pos>& path){
//將入口壓棧
path.push(entry);
//如果棧不為空
while(!path.empty()){
//獲取棧頂元素,即上一次走的路徑
Pos cur = path.top();
//將其標記為已走過
Maze[cur.x*sz+cur.y] = 3;
//找到出口
if(sz-1==cur.x){
return true;
}
Pos next = cur;
//下一步,向右移動
next.x += 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向右移動,將當前步入棧
path.push(next);
continue;
}
next = cur;
//下一步,向左移動
next.x -= 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向左移動,入棧
path.push(next);
continue;
}
//下一步,向上移動
next = cur;
next.y += 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向上移動
path.push(next);
continue;
}
next = cur;
//向下移動
next.y -= 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向下移動
path.push(next);
continue;
}
//上、下、左、右都不能走
path.pop();
}
return false;
}
方式二:通過遞歸
核心代碼
/*
*函數說明:根據遞歸尋找迷宮出口
*參數說明
* Maze:迷宮地圖
* sz:迷宮大小
* entry:迷宮入口
* path:用來判斷是否存在出口的棧
*返回值:無(如果存在出口,棧為空;如果不存在出口,棧中存在起點坐標)
*/
void FindMazePathR(int *Maze,size_t sz,Pos &entry,stack<Pos> & path){
//將入口壓棧
path.push(entry);
Pos cur = entry;
//將已走過的路標記為3
Maze[cur.x*sz+cur.y] = 3;
//找到出口,直接返回
if(sz-1==entry.x){
//將起點坐標彈出
path.pop();
return ;
}
Pos next = cur;
//右
next.x += 1;
if(CheckIsAccess(Maze,sz,next)){
//以當前位置為起點,遞歸進行下一步
FindMazePathR(Maze,sz,next,path);
}
next = cur;
//左
next.x -= 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
//上
next = cur;
next.y += 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
//下
next = cur;
next.y -= 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
path.pop();
}
最后,附上整個程序的完整代碼(代碼量較少,聲明與實現我就不分文件了)
迷宮問題求解完整代碼
//相關函數的聲明與實現
#ifndef __MAZE_H__
#define __MAZE_H__
#include<iostream>
#include<iomanip>
#include<stack>
#include<assert.h>
namespace Maze{
using namespace std;
//迷宮大小
static const int N = 10;
//迷宮地圖文件名
static const char *const FILENAME = "MazeMap.txt";
//坐標
struct Pos{
int x; //橫坐標(本質是數組arr[i][j]的j)
int y; //縱坐標(本質是數組arr[i][j]的i)
};
/*
函數說明:從文件中獲取迷宮地圖
參數說明:
Maze:迷宮地圖數組
sz:迷宮大小
返回值:無
*/
void GetMaze(int *Maze,size_t sz){
FILE *fp = fopen(FILENAME,"r");
//打開失敗
if(NULL==fp){
//輸出錯誤信息
perror(FILENAME);
//結束程序
exit(1);
}
//將文件中的迷宮地圖讀入Maze數組內
for(size_t i=0; i<sz; ++i){
for(size_t j=0; j<sz;){
//從文件流中獲取字符
char tmp = getc(fp);
//字符為0或為1時,導入數組
if(tmp=='0'||tmp=='1'){
Maze[i*sz+j]=tmp -'0';
++j;
}else if(EOF==tmp){
//文件已讀完,循環還未停止
//說明此處文件中的迷宮地圖存在問題
assert(false);
return ;
}
}
}
//關閉文件
fclose(fp);
}
/*
函數說明:打印迷宮
參數說明:
Maze:迷宮地圖數組
sz:迷宮大小
返回值:無
*/
void PrintMaze(int *Maze,size_t sz){
cout<<setw(2);
for(size_t i=0; i<sz; ++i){
for(size_t j=0; j<sz; ++j){
cout<<Maze[i*sz+j]<<setw(2);
}
cout<<endl;
}
}
/*
函數說明:檢測當前位置是否可以通過
參數說明:
Maze:迷宮地圖數組
sz:迷宮大小
cur:當前所在位置
返回值:可以通過返回true,不能通過返回false.
*/
bool CheckIsAccess(int *Maze,size_t sz,Pos cur){
if(cur.x>=0 && cur.x<sz && //行坐標是否越界
cur.y>=0 && cur.y<sz && //列坐標是否越界
Maze[cur.x*sz+cur.y]==0 ){ //所在行列是否可以通過
return true;
}
return false;
}
/*
函數說明:通過棧來進行迷宮求解
參數說明:
Maze:迷宮地圖數組
sz:迷宮大小
entry:迷宮入口點
path:用於尋找迷宮出口的棧
返回值:找到出口返回true,沒找到返回false.
*/
bool FindMazePath(int *Maze,size_t sz,Pos &entry,stack<Pos>& path){
//將入口壓棧
path.push(entry);
//如果棧不為空
while(!path.empty()){
//獲取棧頂元素,即上一次走的路徑
Pos cur = path.top();
//將其標記為已走過
Maze[cur.x*sz+cur.y] = 3;
//找到出口
if(sz-1==cur.x){
return true;
}
Pos next = cur;
//下一步,向右移動
next.x += 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向右移動,將當前步入棧
path.push(next);
continue;
}
next = cur;
//下一步,向左移動
next.x -= 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向左移動,入棧
path.push(next);
continue;
}
//下一步,向上移動
next = cur;
next.y += 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向上移動
path.push(next);
continue;
}
next = cur;
//向下移動
next.y -= 1;
if(CheckIsAccess(Maze,sz,next)){
//可以向下移動
path.push(next);
continue;
}
//上、下、左、右都不能走
path.pop();
}
return false;
}
/*
*函數說明:根據遞歸尋找迷宮出口
*參數說明
* Maze:迷宮地圖
* sz:迷宮大小
* entry:迷宮入口
* path:用來判斷是否存在出口的棧
*返回值:無(如果存在出口,棧為空;如果不存在出口,棧中存在起點坐標)
*/
void FindMazePathR(int *Maze,size_t sz,Pos &entry,stack<Pos> & path){
//將入口壓棧
path.push(entry);
Pos cur = entry;
//將已走過的路標記為3
Maze[cur.x*sz+cur.y] = 3;
//找到出口,直接返回
if(sz-1==entry.x){
//將起點坐標彈出
path.pop();
return ;
}
Pos next = cur;
//右
next.x += 1;
if(CheckIsAccess(Maze,sz,next)){
//以當前位置為起點,遞歸進行下一步
FindMazePathR(Maze,sz,next,path);
}
next = cur;
//左
next.x -= 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
//上
next = cur;
next.y += 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
//下
next = cur;
next.y -= 1;
if(CheckIsAccess(Maze,sz,next)){
FindMazePathR(Maze,sz,next,path);
}
path.pop();
}
}
#endif
迷宮求解測試代碼
#include"Maze.h"
using namespace Maze;
void MazeTest(){
int arr[N][N]; //迷宮地圖
Pos entry = {2,0}; //起點坐標
stack<Pos> path; //棧
GetMaze((int *)arr,N); //將文件中迷宮導入到arr數組中
PrintMaze((int *)arr,N);//打印迷宮
FindMazePath((int *)arr,N,entry,path);//找迷宮出口
cout<<endl<<endl; //換行處理(使界面更整齊)
PrintMaze((int *)arr,N);//打印走過的迷宮
}
int main(){
MazeTest();
return 0;
}
總結:
1.利用棧去尋找迷宮出口,棧內最終會保存從入口到出口的所有路徑.
2.利用遞歸去尋找迷宮出口,傳進去的棧僅僅只是用來判斷迷宮是否有出口,
3.利用遞歸去尋找出口時,因為遞歸的特性,將會遍歷完迷宮內的所有路徑.
最后,還有一個問題:如果一個迷宮存在多條路徑可以到達出口,那么如何得到迷宮到出口的最短路徑???
有機會的話,我將會在下篇文章討論此事.
附上工程文件:http://pan.baidu.com/s/1gfoNrLD
