题目要求
题目的题目大致如下:判断在一个由 0
(通路)和 1
(墙壁)组成的二维迷宫中,是否存在一条从起点到终点的有效路径。我只能上下左右移动,不能穿墙,也不能走出迷宫。
一开始的思路
我的第一反应是使用递归。我构思了一个递归函数 bfs(x_now, y_now, ...)
,它会从当前位置开始,检查相邻的四个方向。如果某个方向是通畅的,我就继续向那个方向递归。如果最终能走到终点,那路径就找到了。
但很快我就发现这个思路不对。虽然我把函数命名为 bfs
,但这种递归的实现方式本质上是深度优先搜索(DFS) 。它会沿着一条路走到死胡同才回头,这和迷宫问题中寻找路径的本质------向外"一层层扩散"------并不相符。而且,递归有一个潜在的风险:如果迷宫很大,或者存在很长的路径,递归的深度可能会导致栈溢出,程序会直接崩溃。我意识到,这种方法并不适合这个场景,于是我放弃了这条路线。
使用 deque
改进后的思路
BFS 的核心思想是"一层层"地探索,回顾之前数据结构的知识点后,我记起BFS一般都是用队列
这个数据结构来实现的。为确保我能最快地找到所有相邻的、可达的位置。我选择了 collections.deque
来实现这个思路,因为它是一个双端队列,能高效地执行 append
(入队)和 popleft
(出队)操作,并且这两个操作时间复杂度都是O(1)。
我的具体步骤是:
- 创建一个
deque
队列,并将起点坐标(x_1, y_1)
入队。 - 为了避免重复访问同一个位置,我选择直接修改迷宫矩阵,将访问过的通路
0
改为1
。 - 然后,我进入一个
while
循环,只要队列不为空,就一直进行搜索。 - 在循环中,我从队列中取出一个位置
(x, y)
,然后检查它的上下左右四个邻居。 - 如果一个邻居满足三个条件:在迷宫范围内、是通路(值不为
1
)、且从未被访问过,我就把这个邻居加入队列。
出现的问题
我按照上述思路实现了代码,但提交后,有一个测试点超时了。这让我很困惑,因为我自认为已经用了正确的 BFS 算法。
出现原因
经过仔细排查,我找到了问题所在:我标记已访问节点的位置不对。
python
# 错误的代码逻辑
q.append((x_start, y_start))
while q:
x, y = q.popleft()
maze[x][y] = 1 # 错误!在这里标记
# ...
# 遍历邻居,将未标记的邻居入队
if ... and maze[next_x][next_y] != 1:
q.append((next_x, next_y))
我是在一个节点出队时 才将它标记为已访问。这意味着,在某个节点 (x, y)
还在队列中等待处理时,如果有其他路径也通向它,它就会被重复地加入队列。这导致队列中塞满了大量重复的坐标,程序做了很多无用功,效率大大降低,最终导致了超时。
解决方法
我意识到,我必须在节点入队时就立即将其标记为已访问。这样,每个可通行的节点只会被加入队列一次,大大减少了重复计算。
我将代码做了如下修改:
python
# 正确的代码逻辑
q.append((x_start, y_start))
maze[x_start][y_start] = 1 # 改动:起点入队时就标记
while q:
x, y = q.popleft()
# ...
for candidate in wait_list:
next_x, next_y = candidate
if in_maze(...) and maze[next_x][next_y] != 1:
q.append(candidate)
maze[next_x][next_y] = 1 # 改动:入队时立即标记
这次修改后,我的代码顺利通过了所有测试点。通过这个过程,我深刻理解了 BFS 中"何时标记已访问"这个细节的重要性,它直接决定了算法的效率。