問題描述:用一個01矩陣來表示一個迷宮,其中0代表通路,1代表障礙物,迷宮爲4連通。問是否存在一條道路從左上角走到右下角。
代碼:
int dirx[4] = {-1,1,0,0};//four directions
int diry[4] = {0,0,-1,1};//four directions
int maze[101][101];//迷宮最大爲100 x 100
int dir_n[101][101];//dir_n[x][y]記錄點(x,y)下一個待擴展的方向
bool vis[101][101];//標記數組
int n,m;//n爲迷宮的行數,m爲迷宮的列數
struct elem//棧中元素的類型
{
//座標
int x;
int y;
//是從上一個點的哪個方向轉移而來的。1上2下3左4右
int from;
};
stack<elem> s;//循環中使用的棧
stack<elem> ans;//因爲棧s中存儲的點序列是與所需答案相反的,所以需要棧ans來“倒”一下
bool dfs()
{
s.push({1,1,0});//將起點加入工作棧,0無意義
vis[1][1] = true;//標記
while(!s.empty())//工作棧非空(這是遞歸繼續進行的條件)
{
elem t = s.top();//訪問棧頂元素
if(dir_n[t.x][t.y] == 4)//如果爲4,說明該點需要退棧了(爲什麼是4?因爲下面i的取值是0,1,2,3)
{
//模擬退工作棧
s.pop();//出棧
vis[t.x][t.y] = false;//取消標記
continue;//進行下一次循環
}
// printf("(%d,%d)\n",t.x,t.y);//輸出當前棧頂點座標(測試)
if(t.x == n && t.y == m)//檢查是否到達終點
{
while(!s.empty())//翻轉答案
{
ans.push(s.top());
s.pop();
}
return true;//成功
}
//dir_n[t.x][t.y]的取值爲0,1,2,3,表示對(t.x,t.y)的擴展的下一步應該是0,1,2,3還是4
//dir_n[t.x][t.y]的初始值爲0(顯然)
for(int i = dir_n[t.x][t.y]; i <= 3; ++i)//dir_n[t.x][t.y]爲4就代表0,1,2,3都擴展完畢了
{
//計算向上下左右走的下一個點的座標
int tx = t.x + dirx[i];
int ty = t.y + diry[i];
if(tx < 1 || tx > n || ty < 1 || ty > m)//越界
{
//最後一個方向(右)是越界的話,無法通過下面的語句修改dir_n[t.x][t.y]!!!(因爲被continue了)
//這是我在編寫時出的一個小Bug
if(i == 3)
dir_n[t.x][t.y] = 4;
continue;
}
//通路且沒被走過
if(maze[tx][ty] == 0 && vis[tx][ty] == false)
{
s.push({tx,ty,i + 1});//加入工作棧
vis[tx][ty] = true;//標記
dir_n[t.x][t.y] = i + 1;//下一次對(t.x,t.y)的擴展從i = i + 1開始
break;//必須在此中斷,相當於進入遞歸
}
//如果能夠執行到這裏且i爲3,說明該點上下左右都不能走
if(i == 3)
dir_n[t.x][t.y] = 4;//確保dir_n[t.x][t.y]的值一定會被改變
}
}
return false;//失敗
}
int main()
{
freopen("in.txt","r",stdin);
cin >> n >> m;//輸入行數和列數
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> maze[i][j];//輸入地圖
if(dfs() == true)
{
printf("成功\n");
//使輸出的答案符合題目要求
printf("(1,1) ");
ans.pop();
while(!ans.empty())
{
elem temp = ans.top();
ans.pop();
printf("%d\n(%d,%d) ",temp.from,temp.x,temp.y);
}
}
else
printf("失敗\n");
return 0;
}
解題方法:
①入棧時需要標記,出棧時需要取消標記。一個點被標記了等價於其在棧中
②爲什麼最後在棧中的點等價於路徑上的點呢?我們想象一棵二叉樹,也就是狀態搜索樹,從根結點到葉子結點的路徑只有一條(樹的性質)。每當從根結點走向葉子結點,工作棧中保存的便是這條路徑,其他不在這條路徑上的結點已經出棧了。從起點到終點便是從根結點到葉子結點的一種特殊情況,從一般推特殊,此時棧中的點就等價於路徑上的點。
③工作棧中保存了函數執行的信息,我們使用循環時就要考慮用數組去記錄這些信息,如數組dir_n
④將遞歸轉爲循環關鍵就是對照遞歸程序,分析每一步的執行過程,進行模擬,從而實現整個過程
⑤在草稿紙上模擬運行一下遞歸的過程,只看代碼的話,連我都覺得有點糊塗。