數據結構上機-迷宮-非遞歸版深度優先搜索

問題描述:用一個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
④將遞歸轉爲循環關鍵就是對照遞歸程序,分析每一步的執行過程,進行模擬,從而實現整個過程
⑤在草稿紙上模擬運行一下遞歸的過程,只看代碼的話,連我都覺得有點糊塗。

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