栈和队列的应用——迷宫问题(深度、广度优先搜索)
一、迷宫问题
给一个二维列表,表示迷宫(0表示通道,1表示围墙)。给出算法,求一条走出迷宫的路径。
1 2 3 4 5 6 7 8 9 10 11 12 | 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、用栈代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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、用队列代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | # -*- 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)] |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术