前言
上期我们说到DFS的经典入门题目——Fibonaci数列的第n项,这次我们来说下DFS或者BFS较为经典的走迷宫题目解法,很多关于迷宫类题目的母题都是这个了,而且也很容易理解,冲冲冲!
我们约定迷宫里出口用S表示,出口用T表示,可以通行(不是一方通行)的格子用点表示,障碍物(即不能通行)的格子用*表示。
类似如下的输入形式:
....s*
.*****
....*.
***..*
.T....
今天我们利用两种思路,分别是DFS和BFS来解决这个问题。
DFS走迷宫
算法思路
DFS方法来解决的思路类似于我们上次说的老鼠走迷宫,真就无限套娃呗……从一条路一直走到底A,如果碰到障碍物,就退回一步到B,在B处尝试完所有的可能方向,如果还不行,继续回退到C,循环往复,直到找到可行的通路。如果没有,则表示没有通路。
这里有几个点需要注意:
- 怎么表示走的过程,实际上就是你在二维数组操作的过程
- 走的过程中坐标移动时不应该超过地图范围
- 迷宫可能有多条通路,应该选择最近的一条
- 回退过程中怎么标记已经查看过的节点避免循环bug
- 最后一个是我们整体的逻辑思路:即上面一段话的程序实现
因为是开始讲解DFS的算法题解,我会尽量详细,让大家没有太多理解负担,本身也是为了通俗易懂的讲解算法知识。
代码实现细节
首先我们来读取迷宫数据并标记入口和出口,以及各种障碍物
# 读取maze数据的行列数
row,col = list(map(int,input().split()))
# 存储maze数据的二维数组
maze_data = []
for i in range(row):
# maze_data.append([s for s in input()])
In = input()
maze_data.append([s for s in In])
#starting point
s = (0, 0)
for r in maze_data:
if 's' in r:
# 获取起点的行列坐标,从0开始计数
s = (maze_data.index(r), r.index('s'))
接下来我们定义走的方向,由于我们只是上下左右移动(四连通),其实就是行列坐标的加一减一:
# 行走方向,四连通方式
direction = [[-1, 0], [0, -1], [1, 0], [0, 1]]
还有走的时候不应该超过maze的范围,即超过范围的行列坐标pass
# check valid coordinates
def check(x,y):
nonlocal row,col
return 0 <= x < row and 0 <= y < col
这里nonlocal关键字修饰变量后标识该变量是上一级函数中的局部变量,且只能用于嵌套函数中,因为我们是在一个函数里定义的check函数,所以适用。一般这么用主要是为了避免使用global这种比较不安全和暴力的全局变量声明方式。
接下来我们来看看一些其他变量的定义:
# 初始化最远路径长度
distance = 100000
# 初始化标记数组,用来避免重复进入节点
max_r_c = max(row, col)
vis = [[0 for _ in range(max_r_c)] for _ in range(max_r_c)]
# 可行通路数量counter
num_paths = 0
vis数组这里的作用类似于给一个节点能不能走打标签,当我们进入节点A,之后进入节点B,那么我们要将vis里对应A的位置标记,否则再遍历B的可行方向时又会回到A,A又会进入B,导致循环bug。
而当我们退出一个节点C的所有子状态时,应该取消vis里对应的C的标记,因为这仅仅表示这条通路经过C无法通走,不表示其他节点不能通过C组成通路。
有了上面的准备,我们可以来进行DFS核心函数的编写:
def dfs(x, y, steps):
"""
maze core function
Args:
x (int): row index start from 0
y (int): col index start from 0
steps (int): current steps from starting point
"""
nonlocal vis, distanc