先介绍一下二叉树的这两个常见的算法,DFS算法(递归)以及BFS算法(层序)。
DFS常用来进行穷举所有路径,而BFS算法常用来寻找最短路径。
比如给你出个问题,让你求最短路径,那么接下来让我们看看用DFS算法与BFS算法求的区别在哪里?
DFS递归遍历算法:(一条路走到黑,遍历完一个树枝再遍历另外一个树枝)
为了方便理解,以下代码我的注释我将会将其生动形象化以及将问题换成如何寻找洞穴最短出口路径。
首先,明确工具:
里程表(currentDepth):记录你当前走了多少步。
最短路径记录器(minDepthValue):随时更新你找到的最短出口路径。
class Solution: 定义一个模板--类似于定义一个流水线 (solution是指解题方案常常这么定义当然solution可以直接写为解题方案这几个字)
def __init__(self): self是指模板的(流水线的)一个例子,比如探险公司是class类那么self就是类的一个实例---探险家(self)
self.minDepthValue = float('inf') 这里让探险家的最短路径设置为无限大--float("inf")
# 记录当前遍历到的节点深度
self.currentDepth = 0 然后记录探险家现在走的步数--0
def minDepth(self, root: TreeNode) -> int: 定义一个最短路径的函数,root:TreeNode这里的意思是明确root的类型,它是一个树节点-->int的意思就是这个函数最后输出的结果是一个整数。
if root is None: 如果root什么都没有那就说明没有洞穴
return 0 直接返回0
# 从根节点开始 DFS 遍历
self.traverse(root) traverse的意思是遍历,self(人-探险家).traverse(遍历)(root)根节点,让人从根节点(也就是洞穴入口)开始遍历。遍历的意思是什么呢?比如说你面前有99扇门,出口就只有一个,那么你一扇扇的找的这个过程就是遍历,包括在山洞中要寻找最短路径,所以要遍历。
return self.minDepthValue 返回探险家所找到的最短路径值
def traverse(self, root: TreeNode) -> None: 与之前的类似,现在定义了一个遍历函数
if root is None: 洞穴没有则
return 返回空值
# 前序位置进入节点时增加当前深度
self.currentDepth += 1 洞穴有则让探险家的工具--里程表的里程加1
# 如果当前节点是叶子节点,更新最小深度
if root.left is None and root.right is None: 如果符合左与右节点都是空值则说明到子节点了
self.minDepthValue = min(self.minDepthValue, self.currentDepth) 更新最短路径
self.traverse(root.left) 继续探索左,右子洞穴
self.traverse(root.right)
# 后序位置离开节点时减少当前深度
self.currentDepth -= 1 离开其中一个洞穴的出口后返回去还要将里程表减一,因为你要的是最短路径下的出口而不单单是出口。
BFS层序遍历算法--逐层扩散(搜完1层搜1层而不是像D那样搜完一个树枝再搜另一个)
class Solution:
def minDepth(self, root: TreeNode) -> int:
if root is None: 如果洞穴不存在
return 0 直接回家
q = deque([root]) 初始化队列让队列从洞穴口开始
# root 本身就是一层,depth 初始化为 1
depth = 1
while q: 这里是指当q存在时
sz = len(q) 这里记录当前的洞穴位置
# 遍历当前层的节点
for _ in range(sz):
cur = q.popleft() 找到队列中最前面的房间-因为popleft代表的就是队列的最前面
# 判断是否到达叶子结点
if cur.left is None and cur.right is None: 如果最前面的方面没有左洞口以及右洞口则说明其是出口
return depth
# 将下一层节点加入队列
if cur.left is not None: 如果其左或右不是空的则继续进行探险
q.append(cur.left)
if cur.right is not None:
q.append(cur.right)
# 这里增加步数
depth += 1 里程表加1
return depth
BFS相比于DFS更适合寻找最短路径,因为其是层渐式,算法可能不需要遍历所有的节点便可以完成任务,而DFS则需要遍历所有的节点。
又因为DFS会遍历所有的节点所以DFS适合寻找所有路径。
DFS vs BFS:二叉树中的路径搜索哲学(附迷宫探险实战)
一、开篇暴击:用场景激活认知(心理学:锚定效应)
想象你在一个多层迷宫中寻找出口:
- DFS(深度优先搜索):像执着的探险家,选择一条岔路走到底,直到碰壁才回头。
- BFS(广度优先搜索):像谨慎的侦察兵,逐层扫荡每个房间,确保不放过任何近路。
核心问题:
- 如果你想穷举所有可能路径,该选谁?
- 如果你想最快找到最短路径,又该选谁?
二、DFS 算法:一条路走到黑的「递归哲学」(心理学:沉没成本效应)
2.1 核心思想:洞穴探险的「里程表法则」
假设你在洞穴中寻找出口,规则是:
- 每进入一个新洞穴,里程表 + 1(前序遍历)。
- 发现出口(叶子节点)时,记录当前里程。
- 原路返回时,里程表 - 1(后序遍历)。
2.2 代码拆解:用探险装备理解变量(具象化注释)
python
class 探险队:
def __init__(self):
self.最短里程 = float('inf') # 初始认为出口在宇宙尽头(无穷大)
self.当前步数 = 0 # 里程表归零
def 找出口(self, 入口: 洞穴节点) -> int:
if not 入口: # 没有洞穴,直接回家
return 0
self.探索(入口) # 从入口开始探险
return self.最短里程 # 带回找到的最短里程
def 探索(self, 洞穴: 洞穴节点) -> None:
if not 洞穴: # 遇到死路,返回
return
self.当前步数 += 1 # 进入洞穴,步数+1(前序位置)
# 发现出口(叶子节点),更新最短里程
if not 洞穴.左洞口 and not 洞穴.右洞口:
self.最短里程 = min(self.最短里程, self.当前步数)
self.探索(洞穴.左洞口) # 优先探索左岔路
self.探索(洞穴.右洞口) # 再探索右岔路
self.当前步数 -= 1 # 离开洞穴,步数-1(后序位置)
2.3 适用场景:适合「不着急,但要全面」的任务
- 场景:
- 游戏地图全探索(如《塞尔达》开塔)。
- 数学问题全排列(如八皇后问题)。
- 缺点:可能绕远路,无法提前终止。
三、BFS 算法:逐层扫荡的「最短路径法则」(心理学:近因效应)
3.1 核心思想:迷宫中的「广播寻人」
假设迷宫每层有多个房间,你通过广播逐层通知:
- 第 1 层:搜索入口所在层的所有房间。
- 第 2 层:搜索入口的子房间层。
- 每发现出口,立即返回当前层数(最早收到消息的一定是最近的)。
3.2 代码拆解:用队列模拟「逐层广播」
python
from collections import deque
class 侦察队:
def 找最近出口(self, 入口: 洞穴节点) -> int:
if not 入口: # 没有洞穴,直接回家
return 0
队列 = deque([入口]) # 初始化广播范围(入口层)
层数 = 1 # 入口在第1层
while 队列: # 还有房间没搜索
本层房间数 = len(队列) # 记录当前层有多少房间
for _ in range(本层房间数):
当前房间 = 队列.popleft() # 取出当前广播的房间
# 发现出口,直接返回当前层数
if not 当前房间.左洞口 and not 当前房间.右洞口:
return 层数
# 将下一层房间加入广播范围
if 当前房间.左洞口:
队列.append(当前房间.左洞口)
if 当前房间.右洞口:
队列.append(当前房间.右洞口)
层数 += 1 # 广播完一层,进入下一层
return 层数
3.3 适用场景:适合「分秒必争」的任务
- 场景:
- 社交网络找最短人脉(如 LinkedIn 一度人脉)。
- 病毒传播模拟(计算最早感染层)。
- 优点:天然适合最短路径问题,无需遍历所有节点。
四、终极对比:DFS vs BFS 的「思维光谱」(心理学:对比效应)
维度 | DFS(递归 / 栈) | BFS(层序 / 队列) |
---|---|---|
核心逻辑 | 一条路走到黑,用递归回溯 | 逐层扩散,用队列维护当前层 |
空间复杂度 | O (h)(h 为树高) | O (n)(最坏情况下存储所有节点) |
最短路径 | 不适合(需遍历所有路径) | 适合(首次发现即最短) |
典型场景 | 全路径枚举、深度优先搜索 | 最短路径、逐层处理、广度优先搜索 |
心理隐喻 | 执着的探险者(不撞南墙不回头) | 高效的指挥官(全局统筹逐层推进) |
五、为什么这两种算法是二叉树的「万能钥匙」?(心理学:原型理论)
- DFS 是二叉树的「递归基因」:每个节点天然包含左右子树,递归遍历是最直观的表达方式。
- BFS 是二叉树的「层次本质」:树的天然分层结构,与队列的先进先出完美契合。
记住:
- 求「所有可能」用 DFS,求「最快最优」用 BFS。
- 面试中 90% 的二叉树问题,都能用这两种算法框架解决。