資料結構::迷宮(一)--棧的一個應用
阿新 • • 發佈:2019-01-01
【前情描述】:我們先來看一張圖片:
(在這張圖片裡我們用“1”來表示牆,用“0”來表示通路。紅色方塊表示入口點,綠色方塊表示出路)
我們要從迷宮的出口開始走找出路,即紅色走到綠色,那麼怎麼解決這個問題呢?彆著急,往下看,聽我細細講解:
【解決迷宮問題】:
方法一:利用棧來解決
思路分析:
1)我們先要給定一個入口點,從入口點開始出發,即先將入口點進行壓棧
2)我們用一個節點來表示棧頂的元素,用此節點來跟蹤我們要走的足跡,每當我們走一步,就將節點進行壓棧
3)但是我們走的時候有四個方向可以走,所以每走一步就要對這四個方向進行判定,是否可以走通
4)當四個方向都可以走通的時候,就重複以上步驟,當四個方向都走不通的時候,我們就出棧,回溯
5)什麼時候結束,當棧為空的時候說明沒有通路,回溯到最初的入口點
6)什麼時候我們找到了一條通路,就是當我們走的路的行等於迷宮的行的時候
接下來,我們直接看程式碼:
#include<iostream> #include<assert.h> #include<stack> using namespace std; //#define N 10 const size_t N = 10; //儘量使用const //迷宮中所走的位置表示 struct Pos { int _row; //所在位置的行 int _col; //所在位置的列 }; //獲取迷宮 void GetMaze(int* maze,size_t n) { FILE* fp = fopen("1.txt","r"); //先通過相對路徑開啟儲存迷宮地圖的文字 assert(fp); //開啟文字是否成功 for(size_t i = 0; i<n; i++) { for(size_t j = 0; j<n;) { int ret = fgetc(fp); if((ret == '0')|| (ret == '1')) { maze[i*n+j] = ret-'0'; //注意開啟的是文字要轉化成讀出的二進位制形式 j++; } //如果迷宮地圖本身不是n*n的,那麼上面的迴圈就會成為死迴圈 if(ret == EOF) { cout<<"出錯!"<<endl; } } } } //檢查此位置是否可以通 bool CheckIsAccess(int* maze,size_t n,Pos pos) { //首先這個位置得合法,其次再是pos這個位置是否為0 if(pos._row>=0 && pos._row<n && pos._col>=0 && pos._col<n && maze[pos._row*n+pos._col] == 0) { return true; } return false; } //求解迷宮路徑 bool GetMazePath(int* maze,size_t n,Pos entry,stack<Pos>& path) { assert(maze); path.push(entry); Pos cur; Pos next; while(!path.empty()) //當棧不為空的時候,才能進行路徑的尋找 { cur = path.top(); maze[cur._row*n+cur._col] = 2; //把走過的這個位置進行標記 next = cur; //在每次進行判斷之後,要將next進行重置 if(next._row == n-1) { return true; //找到了一條通路 } //開始對上下左右進行探測 //對上的探測 next._row -= 1; //檢查路是否可以通 if(CheckIsAccess(maze,n,next)) { path.push(next); continue; } //對下的探測 next = cur; next._row += 1; //檢查路是否可以通 if(CheckIsAccess(maze,n,next)) { path.push(next); continue; } //對左的探測 next = cur; next._col -= 1; //檢查路是否可以通 if(CheckIsAccess(maze,n,next)) { path.push(next); continue; } //對右的探測 next = cur; next._col += 1; //檢查路是否可以通 if(CheckIsAccess(maze,n,next)) { path.push(next); continue; } //到這個位置,說明四個方向都走不通,進行回溯 path.pop(); //直接出棧,即前一個位置,再次上去進行探測 } return false; //棧為空,沒有找到通路 } //列印迷宮 void Print(int* maze, size_t n) { for(size_t i = 0; i<n; i++) { for(size_t j = 0; j<n; j++) { cout<<maze[i*n+j]; } cout<<endl; } } int main() { int maze[N][N]; stack<Pos> path; Pos entry = {2,0}; //輸出迷宮 GetMaze((int*)maze,N); cout<<"是否找到迷宮:"<<GetMazePath((int*)maze,N,entry,path)<<endl; Print((int*) maze,N); return 0; }
方法二:利用遞迴來解決
思路分析:遞迴就是將一個問題不斷劃分成若干個子問題
1)對於這道題,我們依然從入口點出發,開始從入口點判斷,進行四個方向的探測
2)接著我們將要走的下一個位置作為入口點,進行探測,這樣不斷的遞迴
3)當我們沒有找到通路的時候,開始回變數溯遞迴,這裡我們依然不將壓棧的節點不刪除,即(path),用棧來記錄遞迴的路徑
我們來看程式碼:
#include<iostream>
#include<assert.h>
#include<stack>
using namespace std;
#define N 10
//const size_t N = 10;
//迷宮中所走的位置表示
struct Pos
{
/* size_t*/ int _row; //所在位置的行
/*sizt_t*/int _col; //所在位置的列
};
//獲取迷宮
void GetMaze(int* maze,size_t n)
{
FILE* fp = fopen("2.txt","r"); //先通過相對路徑開啟儲存迷宮地圖的文字
assert(fp); //開啟文字是否成功
for(size_t i = 0; i<n; i++)
{
for(size_t j = 0; j<n;)
{
int ret = fgetc(fp);
if((ret == '0')|| (ret == '1'))
{
maze[i*n+j] = ret-'0'; //注意開啟的是文字要轉化成讀出的二進位制形式
j++;
}
//如果迷宮地圖本身不是n*n的,那麼上面的迴圈就會成為死迴圈
if(ret == EOF)
{
cout<<"出錯!"<<endl;
}
}
}
}
//檢查此位置是否可以通
bool CheckIsAccess(int* maze,size_t n,Pos pos)
{
//首先這個位置得合法,其次再是pos這個位置是否為0
if(pos._row>=0 && pos._row<n &&
pos._col>=0 && pos._col<n &&
maze[pos._row*n+pos._col] == 0)
{
return true;
}
return false;
}
//求解迷宮路徑
void GetMazePath_r(int* maze,size_t n,Pos entry,stack<Pos>& path)
{
assert(maze);
path.push(entry);
Pos cur;
Pos next;
cur = entry;
maze[cur._row*n+cur._col] = 2; //把走過的這個位置進行標記
next = cur;
if(next._row == n-1)
{
return ; //找到了一條通路
}
//開始對上下左右進行探測
//對上的探測
next = cur;
next._row -= 1;
//檢查路是否可以通
if(CheckIsAccess(maze,n,next))
{
GetMazePath_r(maze,n,next,path);
}
//對下的探測
next = cur;
next._row += 1;
//檢查路是否可以通
if(CheckIsAccess(maze,n,next))
{
GetMazePath_r(maze,n,next,path);
}
//對左的探測
next = cur;
next._col -= 1;
//檢查路是否可以通
if(CheckIsAccess(maze,n,next))
{
GetMazePath_r(maze,n,next,path);
}
//對右的探測
next = cur;
next._col += 1;
//檢查路是否可以通
if(CheckIsAccess(maze,n,next))
{
GetMazePath_r(maze,n,next,path);
}
//到這個位置,說明四個方向都走不通,進行回溯
path.pop();
}
//列印迷宮
void Print(int* maze, size_t n)
{
for(size_t i = 0; i<n; i++)
{
for(size_t j = 0; j<n; j++)
{
cout<<maze[i*n+j];
}
cout<<endl;
}
}
int main()
{
int maze[N][N];
stack<Pos> path;
Pos entry = {2,1};
//輸出迷宮
GetMaze((int*)maze,N);
GetMazePath_r((int*) maze,N,entry,path);
Print((int*) maze,N);
cout<<"是否找到迷宮:"<<!path.empty()<<endl;
return 0;
}
到這裡,相信聰明的你對迷宮問題一定理解了,對棧的應用又有了更深層次的掌握。
【注意】:(關於本文章有些要說明的)
1)我在實現迷宮的時候,首先是構建了二維陣列,將上述的迷宮儲存下來,但是我實現的時候利用一維陣列進行實現。(二維陣列實際上也是一維陣列)
2)我是將迷宮存在了當前工程下,用相對路徑來讀取迷宮地圖
*讀者可以自己用二維陣列實現一把,讀取迷宮可以採取其他的方式
*這裡最重要的是理解了走迷宮的思想,所以實現方法可以多種多樣嘛