本文適合於對迷宮問題已有初步研究,或閱讀代碼能力較強的人.
因此,如果你對迷宮問題一無所知,請參考其他更詳細的資料.
迷宮問題,是一個對棧(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