# Python 實現走迷宮 ###### tags: `python` > 使用工具 > * vs code > * python 3.7 - 提供本篇文章快速導覽 [ToC] ## 題目說明 給予一 `n x n` 矩陣 `maze` 與起點 `start` 終點 `end`,由起點走到終點並返回路徑與顯示已走訪之地圖。其中 `0` 為可走路徑, `1` 為牆壁,`2` 為已走訪,當走到終點時將該點值設為 `5`。 > 限制條件 > * 迷宮一定至少有一條可走路徑 ## 條件準備 設矩陣 `maze` ```python=1 maze = [ [1, 1, 1, 1, 1], [1, 0, 0, 0, 1], [1, 0, 1, 0, 1], [1, 0, 1, 0, 1], [1, 1, 1, 1, 1], ] ``` 設置起點 `start` 與終點 `end` ```python= start = (1,1) end = (3,3) ``` ## 解題思路 在思考怎麼找出路徑之前,應該要先意識到要如何在陣列中移動,可以移動以後才將已走訪節點做處裡。同時間也會遇到走進死胡同的情況,所以也要另外處理走訪順序。 > 以下提供我的演算法 1. 設定起點位置與終點位置 2. 輸入起點、終點與迷宮 3. 設定一迴圈結束條件為找到終點 4. 尋找四周有沒有可走路徑 5. 如果有就將目前位置設為2 6. 前進到下一個位置 7. 將目前位置加進queue裡 8. 否則目前位置 = queue.pop() 9. 檢查是否走到終點 10. 如果是就將目前位置設為5 11. 結束迴圈 12. 否則回到步驟 1 13. 輸出已走訪後之地圖 ## 實作 首先建立一函式 `findPath` 提供主程式呼叫,呼叫時需傳入 `maze`、`start`、`end` 三個參數,同時建立目前位置之 Hash Table `current` 與紀錄路徑之 deque `q` 並初始化。 ```python=1 from collections import deque def findPath(maze,startP,end): # current 為目前位置,因還沒開始走訪,所以目前位置等於起點 cur = {'x':startP[1],'y':startP[0]} # endP 為終點座標,預先建立方便待會來確認是否到終點 endP = {'x':end[1],'y':end[0]} # q 為紀錄已走訪路徑的資料結構 q = deque({'x':cur['x'],'y':cur['y']}) ``` 接者設置一個 while 迴圈來走訪迷宮,結束目標是走到終點。迴圈結束時顯示地圖並回傳路徑。 ```python=10 while True: # 往下走 if maze[cur['y']+1][cur['x']] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x'],'y':cur['y']+1}) cur['y'] = cur['y'] +1 # 往右走 elif maze[cur['y']][cur['x']+1] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x']+1,'y':cur['y']}) cur['x'] = cur['x'] +1 # 往左走 elif maze[cur['y']][cur['x']-1] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x']-1,'y':cur['y']}) cur['x'] = cur['x'] -1 # 往上走 elif maze[cur['y']-1][cur['x']] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x'],'y':cur['y']-1}) cur['y'] = cur['y'] -1 # 都沒有可走路徑 else: maze[cur['y']][cur['x']] = 2 cur = q.pop() if len(q) == 0: q.append({'x':cur['x'],'y':cur['y']}) # 檢查是否走到終點 if cur['x'] == endP['x'] and cur['y'] == endP['y']: maze[cur['y']][cur['x']] = 5 break # 顯示地圖並回傳路徑 showMaze(maze) return q ``` 最後在設立一個函式 `showMaze` 來輸出地圖狀態即可。 ```python=50 def showMaze(maze): for i in range(len(maze)): for j in range(len(maze[0])): print(maze[i][j],end=' ',sep=',') print() ``` ## 完整程式碼 ```python=1 from collections import deque def showMaze(maze): for i in range(len(maze)): for j in range(len(maze[0])): print(maze[i][j],end=' ',sep=',') print() def findPath(maze,startP,end): cur = {'x':startP[1],'y':startP[0]} endP = {'x':end[1],'y':end[0]} q = deque() q.append({'x':cur['x'],'y':cur['y']}) while True: if maze[cur['y']+1][cur['x']] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x'],'y':cur['y']+1}) cur['y'] = cur['y'] +1 elif maze[cur['y']][cur['x']+1] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x']+1,'y':cur['y']}) cur['x'] = cur['x'] +1 elif maze[cur['y']][cur['x']-1] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x']-1,'y':cur['y']}) cur['x'] = cur['x'] -1 elif maze[cur['y']-1][cur['x']] == 0: maze[cur['y']][cur['x']] = 2 q.append({'x':cur['x'],'y':cur['y']-1}) cur['y'] = cur['y'] -1 else: maze[cur['y']][cur['x']] = 2 cur = q.pop() if len(q) == 0: q.append({'x':cur['x'],'y':cur['y']}) if cur['x'] == endP['x'] and cur['y'] == endP['y']: maze[cur['y']][cur['x']] = 5 break showMaze(maze) print(f'最佳路徑為: {q}') maze = [[1,1,1,1,1], [1,0,0,0,1], [1,0,1,0,1], [1,0,1,0,1], [1,1,1,1,1]] start = (1,1) end = (9,9) findPath(maze,start,end) ``` ## 總結 其實實現以上功能之程式碼的觀念就是 **BFS(廣度優先搜索)**,搭配一點點的 Hash Table 與 deque 即可完成迷宮之走訪,以上的概念與 **Backtracking(回溯法)** 非常相似,但不需要去紀錄完整的路徑,只需將已走訪的點關閉即可找出最短路徑,比起 Backtracking 我個人認為這個作法更接近 **Exhaustive attack** XD。