數據結構之棧的應用----迷宮求解

/***********程序設計思想*************/
(1)迷宮地圖相關:
利用動態二維數組來初步勾勒出迷宮:
建議先用malloc申請一維數組,再用calloc申請每個元素中的一維數組,因爲我用的是1來表示迷宮的通路,0表示死路,calloc申請完後就會自動初始化爲0
迷宮交岔路結點:
我們要有一個掃描通路的函數,對一個座標進行東南西北的掃描,當遇到交岔路的座標時,需要將所有的通路存入一個數組,掃描從東開始,至北結束,逆時針方向
這裏設計的是單通道迷宮,也就是說不會有並行的通路,掃描出來的通路是不會超過兩個的(肯定是不允許把結點來的那個原始方向認爲是通路的)
當我們按着第一個方向走到死路時,我們需要返回到最近的一個交岔路結點,這之後第一個最重要的操作便是:將已確定的死路給封死,即把那個座標的值從1改爲0。
(2)棧相關:
棧在這裏需要兩個,一個存放走過的路徑,也就是移動到一個座標,就把這個座標入棧,另一個是存放交岔路結點的,當我們遇到死路之後,就需要從交岔路結點棧中
彈出一個元素A,然後從所有路徑的棧中一直彈出元素(就是原路返回),直到棧頂元素B與A相等,就表明已經退回到了最近的一個交岔路口
下一步便是走向另一個通路,直到遇到死路或終點
(3)設計根本:
整個程序是建立在遞歸應用中的,抽象出來就是:當我們規定一個優先方向(默認爲東方向吧)、再規定一個固定的旋轉方向(默認爲逆時針)之後,我們會有一個從東方、
沿逆時針、到北方的總體走向,遇到一個死衚衕,我們就要沿着路徑原路返回,直到遇到一個十字路口,再走向另一個可通的方向,要是還是死衚衕,就返回到再前一個十字
路口,進入另一個可通的方向,如此反覆的探索,知道走到終點。
/***********程序設計細節*************/
(1)掃描通道函數----int Scan_Direction(data_type elem,int *all_direction,int **labyrinth);
返回值:返回所有可通方向的數量,可能值爲0,1,2
重要參數:all_direction:記錄所有可通的方向
關鍵實現:
if(labyrinth[elem.x][elem.y+1] == YES && elem.direction != EAST)
{
	direction = EAST;
	all_direction[i++] = EAST;
}
座標是按照二維數組的行列來規定的,這裏給出的是判斷當前座標的東向是可通,YES表明可通,當座標的東鄰座標([elem.x][elem.y+1])值爲YES的時候,我們並不能
確定它的東方向就是可通的,因爲有一種例外:當這個座標就是從它的東鄰座標移過來的時候,這個座標當然是可通的,所以在if的判斷語句中還應加上一個判斷語句:
elem.direction != EAST,只有當這兩個條件同時滿足的時候才表明東鄰爲通,其餘三個方向的判斷與之類似

(2)根據方向的數目來走迷宮

case 1:先看只有一個方向可走到時候

這種情況很好解決,按着掃描的方向走一步就OK,讓移動後的座標入棧即可,這裏我遇到了一個小問題,就是由於很多次的往回走,每一次都要入棧或出棧,貌似這樣可能

造成路徑棧中存在幾個相同的座標,並且這些座標在棧中都是相鄰的,所以我在每次入棧的時候都判斷了一下,如果和棧頂元素相同,就不入棧了,以免最後彈出正確路徑的

時候出現多個重複的座標

case 2:這個條件是程序的關鍵部分,總的來說就是先走一個方向,若未遇到終點,就返回到交岔路結點,再走向另一條路,反覆直到走到終點

case 2:
{
	Push_Stack(&cross_point,position);
	GetElem_Stack(&path,&top_path);
	if(top_path.x != position.x || top_path.y != position.y)
		Push_Stack(&path,position);
	Print_Position(position);
	Move_Path(&position,all_direction[0],&path,&cross_point,labyrinth);
	if(position.x == (LINE-2) && position.y == (ROW-2))
		break;
	Move_Path(&position,all_direction[1],&path,&cross_point,labyrinth);
}
break;
首先讓交岔路結點入棧(交叉點棧),讓這個點入路徑棧

接下來就是走向第一個方向,進入這個方向後,position將先通過all_direction[0]方向移動一格,然後利用遞歸,對新座標進行掃描,判斷出可通方向個數(0,1,2三種),

接着就開始重複昨天的故事,也就是遞歸了,當這一個方向走完後,這個Move_Path就結束了,然後判斷是否到了終點,如果不是終點就進入第二個Move_Path,繼續遞歸,

知道走到終點

這種遞歸過程,完全可以利用二叉樹的圖形來理解,先走完一個方向,再走另一個方向,二叉樹如圖:

箭頭代表單通道,X代表死路

路線;

起點-->1-->3-->(彈出3)1-->4-->(彈出4、1)-->起點....

這是左方向的走法,右方向按着這個方法走下去就能找到終點。

case 0
當我們到了交叉點3的時候,會發現它是一個死衚衕,即可通方向爲0,這個時候就需要不斷的彈出路徑棧的元素,一直到交叉點1,進入交叉點4的方向。
(3)封掉已經確定的死路
當我們彈出交叉點1,返回到起點時,我們就可以確定從交叉點1走到方向的所有路都不可能走到終點,這個時候,我們就可以封掉交叉點1,即
把此座標的值從1變爲0
其實進行第二次Move_Path的時候,座標已經到了交叉點2了,也就是說在交叉點2處掃描的時候,是不會檢測交叉點1的,這樣看來封掉死路似乎
沒有必要,確實是的,但是如果你設計的Move_Path沒有一進入就將座標移到下一個位置的話,這個東西就必要了,也就是你再次在起點掃描可能
會又把交叉點1掃描進去,就造成了一個無限的函數嵌套了。。。
封掉死路只是一個我們思考的一個常識,根據個人而定。
(4)走迷宮循環的第一步(關鍵):

if(count_direction != 0)
{
	count_direction = Scan_Direction(position,all_direction,labyrinth);
}
else
{
	break;
}
這個代碼應該算得上短小精悍,if條件語句表示在上一次循環中不是在case 0的switch語句中結束的,因爲case 0本身就表示掃描的方向爲0,如果這裏沒有這個判斷,
那麼滿足下面兩個條件的時候,程序就會出現問題:
1、上一次循環就是在case 0中結束的;
2、上一次循環存在於case 2中的第一個Move_Path中,而本次循環剛好又要求進入第二個Move_Path
即是:起始座標是一個交叉點,all_direction本身就已經保存了兩個方向,當你按第一個方向走到死路(case 0),你的position重新返回到了交叉點,這個時候你本該
退出這個函數,用all_direction中的第二個方向走下去,但是你卻不可能退出while循環,因爲你的position依舊要進行掃描,並且結果同樣進入case 0,這樣就會陷入死
循環,我們就需要加上這個if-else語句,當count_direction等於0時,就表明是從case 0中來的,就應該break,進入第二個函數

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章