迷宮問題的探討
1.研究背景
前幾天,有同學問我,這道題目怎麼做啊。哪道題目呢?那你就繼續看吧。
2.真題重現
(1)題目描述:
在一家博物館裏,有一件珍貴的珠寶放在一個房間裏。博物館的房間以NxM網格的形式呈現。博物館的所有都與相鄰的房間相鄰。有一名叫山姆的小偷打算偷這件珠寶。他設法弄到了一些房間(包括放置寶石的房間)的鑰匙。他從網格左上角的單元格所表示的房間進入博物館。山姆有鑰匙的房間表示爲1,山姆沒有鑰匙的房間表示爲0,有珠寶的房間表示爲9.
寫一個算法,如果山姆到達有珠寶的房間,輸出1,否則輸出0。
(2)輸入和輸出:
輸入:
該函數/方法 的輸入包括三個參數--------
1)rows,一個整數,表示網格grid中的行數(N);
2)columns,一個整數,表示網格grid中的列數(M);
3)grid,表示一個二維的整數網格。
輸出:
如果山姆到達有珠寶的房間,則返回整數1,否則返回整數0。
(3)注意:
山姆不能對角移動。
(4)示例1:
輸入:
3,3,{ {1,1,1},{9,1,1}, {0,1,0}}
期望的輸出:1
(5)示例2:
輸入:
3,3,{{0,0,0},{9,1,1},{0,1,1}}
期望的輸出:0
3.數學表達
在平面直角座標系的第一象限內,我們來探討如下問題。對於給定的兩個正整數M(M>100)、N(N>100),在第一象限內有任意一點(x,y),滿足x,y爲正整數,且x<M,y<N,有f(x,y)=0或1。有一隻螞蟻從原點(0,0)出發,該螞蟻只能在第一象限內上、下、左、右爬行,且每次爬行的長度爲一個單位,即螞蟻所在的位置(a,b)一定滿足a、b爲自然數。若f(x,y) = 0,則螞蟻不可以到達點(x,y),否則螞蟻可以到達該點。若給定任一點(m,n),有0<m<M,0<n<N,求解該螞蟻是否能夠到達該點的算法。
4.算法設計與分析
(1)分析
經過思考,發現從原點出發的螞蟻(或山姆),要到達指定點(珠寶的位置),應該像走迷宮一樣嘗試所有可能,尋找出一條路徑來爲止。爲說明方便,這裏給每一個座標編號,只是爲了說明方便,在實際的編碼中,不會加入這裏的編號,而是使用座標。如下圖:
我們從左至右,從上至下依次編號。上圖中原點(0,0)的編號爲1;點(1,0)編號爲2;點(2,0)編號爲3;點(0,1)編號爲7。
因此對於0<x<M,0<y<N的範圍,點(x,y)編號爲M*y+x+1。
對於上圖,可以用如下的矩陣描述:
對於走迷宮,我們應該不斷嘗試新的可到達路徑,對於每一個岔路都應該去嘗試,直到到達指定出口,這裏的珠寶位置就是出口;如果走進了死衚衕,我們應該回溯,就是換一條路繼續探索;另外還要防止死循環。綜上,每到達一個岔路我們都應該標記一下,進入死循環或走進死衚衕的時候回到岔路換一條路繼續走。如下圖:
這裏的位置1就是一個岔路,因爲有兩條路可以走,即1->7或1->2。因此1位置要做一個標記。另外,爲避免是循環,如1->2->8->7->1,此時又回到了1位置,因此1位置要做一個標記。
最後,我們每走一步,就在新位置上選擇新的路徑,那麼我們的路徑長度是一個增長的過程,並且增長的過程中還存在捨棄某些路徑重新選取岔路的可能。所以,大體上,路徑長度不斷增加,新的點又可以不斷增加進來或丟棄。因此,我們需要用“先進後出”的數據結構----棧!
(2)棧的push和pop操作
首先從原點出發,也就是編號爲1的點('編號爲N的點'簡稱爲'點N')出發,1入棧,此時棧中只有點1,那麼棧如下圖所示:
然後發現點1是一個岔路,因爲點1可以到達點7,也可以到達點2,那麼1->7,1->2依次入棧。棧如下圖所示
此時,我們到達了點2,發現點2可以連通點8和點3,於是2->8,2->3入棧,棧如下圖所示。
然後我們到達了點3,3只能和點9連通不能和點4連通,於是3->9入棧。假設此時到了3發現是一個死衚衕,即3不能和9連通,也不能和4連通。那麼此時3就應該出棧,出棧後我們應該在路徑1->2->8的基礎上尋找出路。下圖是假設3爲死衚衕時,3出棧後的新棧,如下圖:
我們就這樣反反覆覆地出棧入棧,直到我們到達指定的點,也就是目標點(此處爲點31)出現在棧裏面。或者最後發現棧爲空,說明目標不可達。
5.編碼
(1)設計迷宮棧
這裏,我們使用C++語言來進行編碼。我們需要一個棧,命名爲LabyrinthStack。定義如下
stack<MyPoint *> LabyrinthStack;
可以用一個數組來保存路徑內容,故棧中元素類型爲一個個數組,數組中每個元素是一個點,故爲MyPoint類型。
很容易知道數組最大長度爲M*N,即迷宮中所有的點的個數。MyPoint類型定義爲:
typedef struct myPoint
{
int x;//橫座標
int y;//縱座標
int iFork;//該點是否爲岔口
int nValue;//該點是否可以到達value=1表示可以,value=0表示不可以
int nflag;//該點是否已經入棧,1入棧;0未入棧。
int index;//索引,ID號,其中index = M*y+x+1,MxN表示迷宮矩陣大小
}MYPOINT,*pMYPOINT;
一個二維數組
(2)功能函數
我們需要一個函數,來找到指定點相互連通的函數,命名爲IsAccess。
如下:
/*該函數判斷該點是否可達
輸入:x,點的橫座標
y,點的縱座標
輸出:不可達-1
可達:0
*/
int IsAccess(int x,int y)
{
if(x>=0 && x < M && y>=0 && y<N && grid[x][y] = 1)
{
return 0;
}
else
{
return -1;
}
}
(3)完整編碼
以下C++代碼在linux操作系統上G++未編譯,沒測試通過,功能也沒通過(哈哈哈哈...)。
(未完,待續...)
#include <stack>
#include <iostream>
using namespace std;
typedef struct myPoint
{
int x;//橫座標
int y;//縱座標
int iFork;//該點是否爲岔口
int nValue;//該點是否可以到達value=1表示可以,value=0表示不可以
int nflag;//該點是否已經入棧,1入棧;0未入棧。
int index;//索引,ID號,其中index = M*y+x+1,MxN表示迷宮矩陣大小
}MYPOINT,*pMYPOINT;
int main(int argc, char *argv[])
{
stack<MYPOINT> LabyrinthStack;
int i = 0;
for(i=0;i<10;i++)
{
MYPOINT a;
a.x = i;
a.y = i;
a.iFork = 1;
a.index = i;
s.push(a);
}
cout<<"+=============================================+"<<endl;
while(!s.empty())
{
MYPOINT b = s.top();
s.pop();
}
return 0;
}
/*該函數判斷該點是否可達
輸入:x,點的橫座標
y,點的縱座標
輸出:不可達-1
可達:0
*/
int IsAccess(int x,int y, int M, int N)
{
if(x>=0 && x < M && y>=0 && y<N && grid[x][y] = 1)
{//既然可以到達,那就在這裏入棧唄
return 0;
}
else
{
return -1;
}
}
bool isJewelry(int x,int y,int **grid)
{
if(grid[x][y] == 9)
{
return true;
}
return false;
}
int isPath(int rows, int columns, int **grid)
{//MxN表示迷宮矩陣大小,即rows x columns表示矩陣的大小
MYPOINT pointSet[rows*columns];//所有點。
int i = 0;
int j = 0;
int M = ROWS;
int N = columns;
//初始化所有點
for(i=0;i<M;++i)
{
for(j=0;j<N;++j)
{
pointSet[M*j+i+1].x = i;//橫座標
pointSet[M*j+i+1].y = j;
pointSet[M*j+i+1].iFork = 0;
pointSet[M*j+i+1].nflag = 0;//此處所有點都沒有入棧哦
pointSet[M*j+i+1].nValue = grid[i][j];
}
}
LabyrinthStack.push(pointSet[0]);//從原點出發
while(!LabyrinthStack.empty)
{
MYPOINT topElement = LabyrinthStack.top()
int x = topElement.x;//棧頂元素橫座標 M*y+x+1
int y = topElement.y;//棧頂元素縱座標
int fork = 0;
if(!IsAccess(x+1,y))
{
if(isJewelry(x+1,y,grid))
{
return 1;//已經到達了珠寶的房間
}
LabyrinthStack.push(pointSet[M*y+x+1+1]);
}
if(!IsAccess(x,y+1))
{
if(isJewelry(x,y+1,grid))
{
return 1;//已經到達了珠寶的房間
}
LabyrinthStack.push(pointSet[M*(y+1)+x+1]);
}
if(!IsAccess(x-1,y))
{
if(isJewelry(x-1,y,grid))
{
return 1;//已經到達了珠寶的房間
}
LabyrinthStack.push(pointSet[M*y+x]);
}
if(!IsAccess(x,y-1))
{
if(isJewelry(x,y-1,grid))
{
return 1;//已經到達了珠寶的房間
}
LabyrinthStack.push(pointSet[M*(y-1)+x+1]);
}
if(fork == 0)
{//該點是死衚衕,該點出棧
LabyrinthStack.pop(pointSet[M*y+x+1]);
}
}
return 0;//棧爲空,山姆找不到通向珠寶房間的路線,返回0
}