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

来源:http://www.cnblogs.com/mingjiatang/archive/2016/10/13/5958219.html
-Advertisement-
Play Games

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


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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 花點時間整理下sql基礎,溫故而知新,也方便複習查看。文章的demo來自oracle自帶的dept,emp,salgrade三張表。解鎖scott用戶,使用scott用戶登錄就可以看到自帶的表。 #使用oracle用戶登錄linux [oracle@localhost ~]$ sqlplus / a... ...
  • ...
  • 80%的前500強企業就數據管理方面都有一個共性——管理規範,高效輔助流程。 但數據管理並不是一言即成,尤其是處於快速發展和轉型的企業。就數據系統而言,一旦系統增多,相應的數據問題也隨之而來。那麼如何統一有效地管理數據?實現數據可視化?這裡分享某百強集團搭建數據平臺的建設經驗。 ...
  • truncate table page_frame_mst; select setval('page_frame_mst_id_seq', 1, false); select setval('image_group_mst_id_seq', (select max(id) from image_gr ...
  • 最近接手些mysql資料庫維護,發現mysql在批量操作方面就是個渣渣啊,比起MS SQL SERVER簡直就是“不可同日而語”。 咨詢了下MySQL的高手,對於數據遷移這種問題,一種處理方式就是直接“一步到位” ,一次性將所有數據查詢插入到另外一個表,然後再刪除原表數據;另外一種處理方式就是使用p ...
  • 最近做項目在部署到阿裡雲伺服器上之後出現了兩個問題: 1、亂碼問題。 2、ajax的php處理頁面裡面利用json_encode()函數返回json數據,則資料庫返回的數據只能是UTF8,如果是gbk則json也無法返回。 發現是資料庫編碼格式問題,網站使用的編碼格式為UTF8,資料庫的編碼格式調為 ...
  • 下麵,主要是驗證在MySQL主從複製環境下,存儲過程,函數,觸發器,事件的複製情況,這些確實會讓人混淆。 首先,創建一張測試表 存儲過程 創建存儲過程 通過查看二進位日誌,可以看到該DDL語句已被記錄 執行存儲過程 查看二進位日誌中,記錄的是還是call p1('tom',10)操作記錄對應的SQL ...
  • 1.出錯結果:資料庫表視圖有多條數據,在使用EF框架進行查詢時卻只得到一條數據(註:攔截EF得到的sql語句在資料庫進行查詢並沒有任務問題)。 2.出錯原因:該視圖中沒有ID或者主鍵,EF查詢時進行反射預設都是同一條數據。 3.總結:EF框架查詢視圖時需要註意加入ID或者主鍵 以上僅是個人簡單分析。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...