迷宮問題

問題描述

給定一個迷宮,用數組表示,其中0表示沒有障礙,1表示有障礙,尋求一個從左上角到右下角的路徑。node

maze = [[0, 1, 0, 0, 0],
        [0, 1, 0, 1, 0],
        [0, 0, 0, 0, 0],
        [0, 1, 1, 1, 0],
        [0, 0, 0, 1, 0]]

路徑是否存在的問題(使用遞歸的方法來判斷)

假如只是尋找是否是存在一個路徑,這個時候能夠使用遞歸的算法。
遞歸算法實際上是將全部的可能路徑都試探一下,只要一條路徑試探成功,那麼就會返回True,當全部路徑都不成功的時候返回False。python

def find_maze(maze, i, j, row, col):
    """
    Args:
      maze: 迷宮(二維數組)
      i,j: 路徑開始的座標,爲(0,0)
      row,col: 矩陣的長和寬,也是路徑結束的座標
    Returns:
      boolean: 是否存在路徑
    """
    if i < 0 or i >= row or j < 0 or j >= col or maze[i][j] != 0:
        return False

    maze[i][j] = 2  # 標記表示已經走過
    if i == row-1 and j == col-1:
        return True  # 已經成功到達
    if (find_maze(maze, i - 1, j, row, col) or
        find_maze(maze, i + 1, j, row, col) or
        find_maze(maze, i, j + 1, row, col) or
        find_maze(maze, i, j - 1, row, col)):
        return True
    return False

使用深度優先搜索(DFS)尋找一條路徑

能夠使用深度優先搜索來尋找一條路徑,這條路徑並不必定是最短路徑。它使用是一種回溯的方法,若是沒有遇到障礙,那麼就一直向前尋找,直到路走不通,那麼回溯到剛開始進入這條路徑的節點。算法

具體的作法是使用一個棧來保存第一個節點的信息,而後棧不爲空的時候進入循環:不斷的尋找能夠行走的路徑存入到棧當中,當發現沒有可走的路徑的時候出棧。最後若是到達目的節點,由於棧中前一個元素是後一個元素的前驅節點,因此棧中保存的內容就是一條路徑。數組

def find_available_point(maze, i, j, row, col):
    """一旦發現一個當前能夠到達的節點就返回"""
    if i+1 < row and maze[i+1][j] == 0:
        return [i+1, j]
    if i-1 >= 0 and maze[i-1][j] == 0:
        return [i-1, j]
    if j + 1 < col and maze[i][j+1] == 0:
        return [i, j+1]
    if j - 1 >= 0 and maze[i][j-1] == 0:
        return [i, j-1]
    return [-1, -1]

def find_maze_use_stack_with_path(maze, i, j, row, col):
    """使用棧來進行回溯法,當棧不爲空的時候進行循環
    循環的內容是:將棧頂的元素彈出來,而後將全部可能的結果進入棧中
    """
    path = [[i,j]]
    maze[i][j] = 2
    while path:
        i, j = path[-1]
        if i == row-1 and j == col - 1:
            return path
        available_point = find_available_point(maze, i, j, row, col)
        if available_point[0] != -1:
            maze[available_point[0]][available_point[1]] = 2
            path.append(available_point)
        else:
            path.pop()
    return path

使用寬度優先搜索(BFS)獲得最短的路徑

寬度優先搜索的策略是一步一步不斷的向外擴展,而不是像深度優先搜索同樣沿一條路徑一直探尋,探尋不到的時候回溯。因此,若是寬度優先搜索到達終點,那麼它所探尋的路徑必定是最短的。app

寬度優先搜索須要結合隊列來使用,它不像棧那樣能夠直接保存前驅節點的信息,因此還須要記錄節點的前驅節點。這裏使用一個和迷宮同樣大的數組來保存前驅節點的信息,也能夠使用字典來保存這一信息。code

from queue import Queue
def find_all_available_point(maze, i, j, row, col):
    """找到當前節點能夠一步到達的全部節點"""
    result = []
    if i+1 < row and maze[i+1][j] == 0:
        result.append([i+1, j])
    if i-1 >= 0 and maze[i-1][j] == 0:
        result.append([i-1, j])
    if j + 1 < col and maze[i][j+1] == 0:
        result.append([i, j+1])
    if j - 1 >= 0 and maze[i][j-1] == 0:
        result.append([i, j-1])
    return result

def get_path(pre_nodes, i, j):
    """根據前驅節點來獲得最終的路徑,i,j爲終節點
       初始節點的前驅節點爲它自己,可做爲遞歸結束的條件
    """
    path = [[i, j]]
    while pre_nodes[i][j] != [i, j]:
        i, j = pre_nodes[i][j]
        path.append([i,j])
    return path

def find_maze_use_queue_with_path(maze, i, j, row, col):
    q = Queue()
    pre_nodes  = [[0 for x in range(col)] for x in range(row)]
    pre_nodes[i][j] = [i, j]
    maze[i][j] = 2
    q.put([i, j])

    while not q.empty():
        i, j = q.get()
        if i == row-1 and j == col-1:
            path = get_path(pre_nodes, i, j)
            path = path[::-1] # 翻轉一下
            return path

        available_points = find_all_available_point(maze, i, j ,row, col)
        if available_points:
            for available_point in available_points:
                pre_nodes[available_point[0]][available_point[1]] = [i, j]  # 記錄前驅節點
                maze[available_point[0]][available_point[1]] = 2
                q.put(available_point)
    return []

if __name__ == '__main__':
    maze = [[0, 1, 0, 0, 0],
            [0, 1, 0, 1, 0],
            [0, 0, 0, 0, 0],
            [0, 1, 1, 1, 0],
            [0, 0, 0, 1, 0]]
    x = find_maze_use_queue_with_path(maze, 0 ,0 , 5, 5)
    print(x)
相關文章
相關標籤/搜索