深度优先搜索(DFS)是一种用于遍历或搜索树、图等数据结构的算法,其核心思想是“一条路走到底,遇到死路就回头”。DFS通过递归或栈的方式实现,优先探索一条路径直到无法继续,然后回溯到上一个岔路口选择另一条路径。这种方法适合寻找所有路径或判断连通性,但不一定找到最短路径。DFS在迷宫寻路、图的连通性判断、岛屿数量问题等场景中有广泛应用。与广度优先搜索(BFS)相比,DFS更注重深度探索,而BFS则更注重广度扩散。DFS的实现需要注意防止死循环和递归深度问题,通常通过标记已访问节点来避免重复探索。
一、迷宫探险家的故事
想象你是一个勇敢的探险家,站在迷宫的入口。你的目标是找到出口,或者探索所有的通道。
你有一条“探险原则”:
- 每次遇到岔路口,都优先选择一条路一直走到底,直到走不通为止。
- 如果遇到死路,就原路返回到上一个岔路口,换一条路继续走。
- 直到所有路都走过,或者找到出口。
这就是**深度优先搜索(DFS)**的核心思想!
二、DFS的过程(图解版)
假设迷宫如下(S是起点,E是出口):
S - A - B - E
|
C
探险家怎么走?
- 从S出发,走到A。
- 从A有两条路:先选A-B。
- 从B走到E,找到出口,任务完成!
- 如果B-E是死路,返回A,换A-C继续探索。
每次都“走到底”,走不通就回头。
三、DFS的实现方式
1. 递归(像套娃一样)
每到一个新路口,就“递归”地往下走,走不通就返回。
伪代码:
def dfs(当前位置):
标记当前位置为已访问
如果当前位置是出口:
结束
对于当前位置的每一个相邻点:
如果没走过:
dfs(相邻点)
2. 用栈(stack)
你可以用一个“栈”来记录走过的路。每次走新路就把位置压栈,遇到死路就弹栈回退。
四、DFS的特点
- 优先走到底,不怕走弯路。
- 适合找“是否有路可达”,或者“所有路径”。
- 可能不是最短路(有更短的路但你没走到)。
- 适合用递归或栈实现。
五、生活化比喻
- DFS像什么?
像你在迷宫里“钻牛角尖”,每次都一条路走到黑,走不通再回头。 - BFS(广度优先)像什么?
像你带着一群小伙伴,大家一起一层一层地探索,先把离入口最近的地方都走一遍。
六、DFS口诀
一条路,走到底,
遇到死路就回去。
所有岔路都要试,
深度优先不放弃!
七、DFS的应用
- 迷宫寻路
- 图的连通性判断
- 拓扑排序
- 岛屿数量问题
- 递归遍历树结构
我们继续用“迷宫探险家”的故事,深入讲解深度优先搜索(DFS),并带你了解它的更多细节、代码实现、常见变种和与BFS的对比。
一、迷宫探险家DFS的详细过程
1. 递归版探险
你每到一个新房间(格子),就:
- 先在门口做个记号(标记已访问)。
- 看看是不是出口,是就欢呼!
- 不是出口,就挑一条没走过的路继续走。
- 走到死路就原路返回,换另一条路。
递归的本质:每次“深入”一个新房间,等探索完再“退回”上一个房间。
2. 栈版探险
你带着一个背包(栈),每走一步就把当前位置放进背包。遇到死路就从背包里拿出上一个位置,回头。
二、DFS代码实现(以二维迷宫为例)
假设迷宫是一个二维数组,0表示路,1表示墙。
1. 递归实现
def dfs(maze, x, y, visited):
# 越界或遇到墙或已访问
if x < 0 or x >= len(maze) or y < 0 or y >= len(maze[0]) or maze[x][y] == 1 or visited[x][y]:
return False
# 到达出口
if (x, y) == (出口坐标):
print("找到出口!")
return True
visited[x][y] = True
# 四个方向探索
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
if dfs(maze, x+dx, y+dy, visited):
return True
return False
2. 栈实现(非递归)
def dfs_stack(maze, start, end):
stack = [start]
visited = set()
while stack:
x, y = stack.pop()
if (x, y) == end:
print("找到出口!")
return True
if (x, y) in visited:
continue
visited.add((x, y))
for dx, dy in [(-1,0),(1,0),(0,-1),(0,1)]:
nx, ny = x+dx, y+dy
if 0 <= nx < len(maze) and 0 <= ny < len(maze[0]) and maze[nx][ny] == 0:
stack.append((nx, ny))
return False
三、DFS的常见变种
-
全路径搜索
想找出所有从入口到出口的路径?DFS可以在递归时记录路径,走到出口时保存下来。 -
计数问题
比如“岛屿数量”问题,每遇到一个新岛屿就用DFS把它全部标记。 -
图的连通分量
用DFS遍历所有节点,统计有多少个“独立区域”。
四、DFS与BFS的对比
特点 | DFS(深度优先) | BFS(广度优先) |
---|---|---|
探索方式 | 一条路走到底 | 一圈一圈地扩散 |
适合场景 | 找所有路径、连通性 | 找最短路径 |
实现方式 | 递归/栈 | 队列 |
可能结果 | 不是最短路径 | 一定是最短路径 |
空间消耗 | 递归深度/栈深度 | 队列长度 |
口诀:
DFS钻牛角尖,BFS广撒网。
五、DFS的注意事项
- 防止死循环:一定要标记已访问,否则会在环形迷宫里转圈圈。
- 递归深度:递归太深可能会栈溢出,Python默认递归深度有限。
- 路径记录:如果要输出路径,可以在递归参数里加上当前路径列表。
六、DFS应用举例
- 迷宫寻路
- 图的连通性
- 岛屿数量
- 拓扑排序
- 组合/排列问题(如八皇后、数独)
七、DFS动画演示推荐
八、DFS探险家口诀升级版
一条路,走到底,
遇到死路就回去。
所有岔路都要试,
深度优先不放弃!
递归栈,记得清,
防止死循环,标记行!