棧和隊列的應用——迷宮問題(深度、廣度優先搜索)


一、迷宮問題

  給一個二維列表,表示迷宮(0表示通道,1表示圍牆)。給出算法,求一條走出迷宮的路徑。

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

  1代表牆,0代表路,圖示如下:

  

二、棧——深度優先搜索

  應用棧解決迷宮問題,叫做深度優先搜索(一條路走到黑),也叫做回溯法

1、用棧解決的思路

  思路:從上一個節點開始,任意找下一個能走的點,當找不到能走的點時,退回上一個點尋找是否有其他方向的點。

  使用棧存儲當前路徑。后進先出,方便回退到上一個點。

2、用棧代碼實現

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

# 四個移動方向
dirs = [
    lambda x,y: (x+1, y),   # 下
    lambda x,y: (x-1, y),   # 上
    lambda x,y: (x, y-1),   # 左
    lambda x,y: (x, y+1)    # 右
]


def maze_path(x1, y1, x2, y2):   # (x1,y1)代表起點;(x2,y2)代表終點
    stack = []
    stack.append((x1, y1))
    while(len(stack)>0):
        curNode = stack[-1]   # 當前的節點(棧頂)
        if curNode[0] ==x2 and curNode[1] == y2:  # 判斷是否走到終點
            # 走到終點,遍歷棧輸出路線
            for p in stack:
                print(p)
            return True

        """搜索四個方向"""
        for dir in dirs:
            nextNode = dir(curNode[0], curNode[1])
            # 如果下一個階段能走
            if maze[nextNode[0]][nextNode[1]] == 0:
                stack.append(nextNode)    # 將節點加入棧
                maze[nextNode[0]][nextNode[1]] = 2   # 將走過的這個節點標記為2表示已經走過了
                break   # 找到一個能走的點就不再遍歷四個方向
        else:
            # 一個都找不到,將該位置標記並該回退
            maze[nextNode[0]][nextNode[1]] = 2
            stack.pop()
    else:
        print("沒有路")
        return False

maze_path(1,1,8,8)

"""
(1, 1)  (2, 1)  (3, 1)  (4, 1)  (5, 1)  (5, 2)  (5, 3)  (6, 3)  (6, 4)
(6, 5)  (7, 5)  (8, 5)  (8, 6)  (8, 7)  (8, 8)
"""

  總結算法就是:創建一個空棧,首先將入口位置進棧。當棧不空時循環:獲取棧頂元素,尋找下一個可走的相鄰方塊,如果找不到可走的相鄰方塊,說明當前位置是死胡同,進行回溯(就是講當前位置出棧,看前面的點是否還有別的出路)

  使用棧來解決迷宮問題,雖然實現起來比較簡單,但是它的路徑並不是最短的,很可能會繞遠,如果想走最短路徑可以使用隊列來做。

三、隊列——廣度優先搜索

  應用隊列解決迷宮問題,叫做廣度優先搜索

1、用隊列解決思路

  思路:從一個節點開始,尋找所有接下來能繼續走的點,繼續不斷尋找,直到找到出口。

  使用隊列存儲當前正在考慮的節點。整體過程如圖所示:

  

  創建一個空隊列,將起點1放入隊列,然后1只有一條路可走,因此1出列2進列,到3入列后由於有兩條路可走,3出列4、5入列;隨后先走4的方向4出列6入列,再5出列7入列,此時6、7在隊列中,6又有了兩個方向,此時6出列,8、9入列,此時隊列中為7\8\9,以此規律依次類推,直到找到出口。

  隊列中存的不再是路徑,而是現在考慮的路,分岔的中端。因此輸出路徑會比較麻煩。

2、輸出路徑方法

  需要一個額外的列表記錄哪個點讓哪個點加入進來,從終點往前推導得出迷宮路徑。

3、用隊列代碼實現

# -*- coding:utf-8 -*-
__author__ = 'Qiushi Huang'

from collections import deque   # 引入隊列

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

# 四個移動方向
dirs = [
    lambda x,y: (x+1, y),   # 下
    lambda x,y: (x-1, y),   # 上
    lambda x,y: (x, y-1),   # 左
    lambda x,y: (x, y+1)    # 右
]


def print_r(path):
    """打印路徑"""
    curNode = path[-1]    # 最后一個節點
    realpath = []         # 出去的路徑
    while curNode[2] != -1:   # 判斷最后一個節點的標記是否為-1,如果是-1說明是起始點,如果不是-1就繼續查找
        realpath.append(curNode[0:2])   # 拿到並添加節點x,y坐標信息
        curNode = path[curNode[2]]      # 這里curNode[2]是當前節點的前一步節點的標識:path的下標,因此path[curNode[2]]拿到前一節點

    realpath.append(curNode[0:2])       # 在這里curNode[2] == -1,是迷宮起點,將坐標信息加入路徑

    realpath.reverse()    # 將列表倒序,將前面生成的從后往前的列表變為從前往后
    print(realpath)


def maze_path_queue(x1, y1, x2, y2):   # (x1,y1)代表起點;(x2,y2)代表終點
    """用隊列實現迷宮問題——深度優先搜索"""
    queue = deque()   # 創建隊列
    queue.append((x1, y1, -1))    # 加入起點,第三個參數是記錄時誰讓它來的,這里起始點設置為-1
    path = []   # 保存出隊節點
    while len(queue) > 0:   # 只有隊不空就一直循環
        curNode = queue.pop()  # 隊首節點出隊,並存為當前節點變量
        path.append(curNode)   # 添加到path列表
        if curNode[0] == x2 and curNode[1] == y2:   # 判斷是否找到終點
            print_r(path)   # 如果到達終點,打印路徑
            return True

        for dir in dirs:    # 搜索四個方向
            nextNode = dir(curNode[0], curNode[1])    # curNode[0],curNode[1]分別是當前節點x、y
            if maze[nextNode[0]][nextNode[1]] == 0:   # 如果有路可走
                queue.append((nextNode[0], nextNode[1], len(path) - 1))   # 后續節點進隊,標記誰讓它來的:path最后一個元素的下標
                maze[nextNode[0]][nextNode[1]] = 2   # 設置為2,標記為已經走過

    else:   # 如果隊列為空(當前節點到了死路,節點刪除沒有新節點加入),沒有路
        print("沒有路")
        return False


maze_path_queue(1,1,8,8)
# [(1, 1), (1, 2), (2, 2), (3, 2), (3, 1), (4, 1), (5, 1), (5, 2), (5, 3), (6, 3), (6, 4), (6, 5), (5, 5), (5, 6), (5, 7), (5, 8), (6, 8), (7, 8), (8, 8)]

  

 


免責聲明!

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



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