迷宮問題求解之“窮舉+回溯”(一)


求迷宮從入口到出口的所有路徑是一個經典的程序設計問題,求解迷宮,通常采用的是“窮舉+回溯”的思想,即從入口開始,順着某一個方向出發,若能夠走通,就繼續往前走;若不能走通,則退回原路,換一個方向繼續向前探索,直到所有的通路都探尋為止。因此本文依據這種“窮舉+回溯”的思想,設計一個求解迷宮的程序。

1 問題分析

為了保證在任何位置上都能夠退回原路,顯然需要使用一個先進后出的數據結構來保存已經探尋過的位置,因此在程序求解迷宮路徑的過程中采用這種數據結構。

迷宮是一個二維地圖,其中含有出口和入口,障礙點和通道,因此程序采用一個二位數組map來表示,其中,二維數組值所代表的含義如下:

0:通道    1:起點    2:障礙    3:終點    4:路徑

需要注意的是,最后求解出來的通路,必須要是一條簡單路徑,即在求得的路徑上不能重復出現同一通道快。下面簡述一下程序的求解過程。

將“在搜索過程中某一個時刻所在迷宮地圖中的位置“記為“當前位置”,那么求解迷宮路徑算法的基本思想是:若“當前位置”可通行,那么將“當前位置”納入求解路徑中(壓到棧中),並朝“下一個位置”繼續探索,即把“下一位置”切換到“當前位置”,如此重復下去,直到找到出口;如果當前位置不可以通行,則順着來的方向退回到“前一個通道位置”,然后再朝着其他方向探索下去;如果該通道塊的4個方向(東西南北)都不可通,那么就在該路徑(棧)中刪除該通道位置(刪除棧頂元素),再繼續退回到“前一個通道位置”繼續探索,即再獲取棧頂元素作為“當前位置繼續上述的重復探索。

2 算法描述

根據對迷宮問題的分析,求迷宮中一條從入口到出口的路徑算法描述如下:

  1. 設定入口位置為當前位置。

  2. if(當前位置是可通行且是沒有被走過的通道塊(分支1)

    2.1 將當前位置壓到棧中。
    
    2.2 若當前位置是出口,`則程序結束,返回路徑棧`。
    
    2.3 若當前不是出口,則切換當前位置為當前位置東邊的通道塊,`返回2繼續執行`。
    
  3. else(若當前位置不可通行)(分支2)

    3.1 獲取棧頂元素所在的位置,若該位置所在的相鄰4個方向都被探索過了,則刪除當前棧頂元素,獲取新的棧頂元素,直到找到一個可通的相鄰塊位置或出棧至棧空`再執行3.2步驟`。
    
    3.2 若棧頂元素所在的位置還有其他方向沒有被探索過,則設定新的當前位置為棧頂元素的未被探索方向的相鄰通道塊。(方向訪問順序為順時針即東南西北)
    
  4. 重復步驟2.

需要注意步驟2中的沒有被走過的通道塊,是指該通道塊從來未壓入棧中,否則求解的路徑就不是一條簡單路徑,很可能導致一個死循環。

3 程序實現

程序采用c#語言對上述的算法描述進行了實現,代碼如下。

3.1 通到塊實體

class PathElement
{
    public PathElement(int row, int col, int direcation)
    {
        this.Row = row;
        this.Col = col;
        this.Direction = direcation;
    }
    public int Row;//位置所在的行
    public int Col;//位置所在的列
    public int Direction;//從此位置走向下一位置的方向,方向分為東0南1西2北3,默認東方向為初始方向
}

3.2 尋找簡單路徑

public static Stack<PathElement> FindPath(int[,] map, int startX, int startY, int endX, int endY)
{
    Stack<PathElement> path = new Stack<PathElement>();
    List<PathElement> visitedList = new List<PathElement>();//保存以通過的元素
    PathElement curPosition = new PathElement(startX, startY, 0);
    int tryCount = 0;
    do
    {
        tryCount++;
        if (Pass(curPosition, map) && (!Visited(curPosition, visitedList)))//當前位置能通過,且沒被訪問過
        {
            path.Push(curPosition);//加入路徑
            if (curPosition.Row == endX && curPosition.Col == endY)
                return path;
            visitedList.Add(curPosition);//該位置以被訪問
            curPosition = GetNextPosition(curPosition);//獲取下一個當前位置,並更新該位置的下一個位置的方向

        }
        else//當前不能通過
        {
            if (path.Count != 0) //如何棧不為空
            {
                PathElement topElement = path.Peek();//獲取棧頂元素
                while (topElement.Direction == 4 && (path.Count > 1))//找尋一個可用的位置
                {
                    path.Pop();
                    topElement = path.Peek();
                }
                if (topElement.Direction < 4)
                {
                    curPosition = GetNextPosition(topElement);//獲取下一個當前位置,並更新該位置的下一個位置的方向
                }
            }
        }
    } while (path.Count != 0);
    return null;
}

3.3 輔助方法

/*獲取下一個位置*/
private static PathElement GetNextPosition(PathElement curPostion)
{
    PathElement nextPosition=null;
    switch (curPostion.Direction)
    { 
        case 0:
            nextPosition = new PathElement(curPostion.Row, curPostion.Col+1, 0);
            break;
        case 1:
            nextPosition = new PathElement(curPostion.Row+1, curPostion.Col, 0);
            break;
        case 2:
            nextPosition = new PathElement(curPostion.Row, curPostion.Col-1, 0);
            break;
        case 3:
            nextPosition = new PathElement(curPostion.Row-1, curPostion.Col, 0);
            break;
    }
    curPostion.Direction++;
    return nextPosition;
}

/*是否為通道*/
private static bool Pass(PathElement curPosition, int[,] map)
{
    int rowCount = map.GetLength(0);
    int colCount = map.GetLength(1);
    //邊界判斷
    if (curPosition.Row >= 0 && curPosition.Row < rowCount && curPosition.Col >= 0 && curPosition.Col < colCount)
    {
        //障礙判斷
        if (map[curPosition.Row, curPosition.Col] == 2)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    else
    {
        return false;
    }
}

/*是否被訪問*/
private static bool Visited(PathElement curPosition, List<PathElement> visitedList)
{
    foreach (PathElement element in visitedList)
    {
        if (element.Row == curPosition.Row && element.Col == curPosition.Col)
        {
            return true;
        }
    }
    return false;
}

3.4 界面

該部分代碼只是提供算法驗證的可視化界面,與本文所探討的算法實現沒有多大的關系,程序的源代碼可以在文章后面的鏈接進行下載。

界面所包含的功能:

  1. 能夠編輯迷宮地圖,單擊實現障礙物的設計,雙擊去掉障礙物。
  2. 能夠保存和載入地圖。
  3. 能夠通過該算法實現從迷宮入口到出口路徑的生成和顯示。

3.5 結果截圖

1 地圖的載入

2016_10_b896248b-8212-4670-b004-6d2ea7fabd9f

2 路徑尋找

2016_10_62fb9a82-2184-43e1-a47f-7cc77bfb2e92

4 總結

“窮舉+回溯”的思路還是很好理解,通俗的講,就是選擇一條道路一直走到黑,如果碰到前方有障礙,就退回來一步再換一個方向繼續走,直到把所有可能的路都做完了。

利用“窮舉+回溯”搜索路徑,雖然簡單,但是它屬於一種盲目、機械的搜索算法,從3.5中的結果圖中可以看出,該算法找出來的路徑,顯然不是最優的,走了許多的彎路。那么如何從迷宮的起點到終點找到一條最優的路徑呢?請參考《迷宮問題求解之“A*搜索”(二)》

5 資源和參考資料

參考資料:嚴蔚敏《數據結構c語言版》

源代碼下載:http://download.csdn.net/detail/mingge38/9653205


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM