树 | 深度优先搜索 | 广度优先搜索 | 二叉树
深度优先搜索(DFS,Depth-First Search)是一种用于遍历或搜索树或图的算法。DFS 从根节点开始,沿着树的深度尽可能向下搜索,直到到达叶子节点,然后回溯到最近的分支点,继续探索其他路径。
DFS 的基本概念
-
遍历方式:
- DFS 可以使用递归或迭代的方式实现。
- 递归方式自然地利用了函数调用栈,而迭代方式通常使用显式栈来模拟递归的调用。
-
应用场景:
- DFS 适用于解决很多问题,如路径查找、连通性检测、拓扑排序、图的遍历、求解迷宫等。
- 在二叉树中,DFS 常用于遍历树的节点,例如前序遍历、中序遍历、后序遍历等。
DFS 的一般解题流程
在二叉树场景下,DFS 的一般解题流程可以分为以下几个步骤:
-
选择遍历方式:
- 确定使用递归还是迭代来实现 DFS。
- 根据问题的要求选择遍历顺序(前序、中序、后序)。
-
定义递归函数或迭代结构:
- 如果使用递归,定义一个函数,接收当前节点及其他必要参数(如结果数组)。
- 如果使用迭代,初始化一个栈来存储节点。
-
递归或迭代逻辑:
- 递归:
- 如果当前节点为空,返回(终止条件)。
- 处理当前节点(如记录值、进行计算等)。
- 递归访问左子树。
- 递归访问右子树。
- 迭代:
- 使用循环,直到栈为空。
- 将当前节点压入栈,访问左子节点。
- 弹出栈顶节点,处理该节点,然后访问右子节点。
- 递归:
-
处理结果:
- 根据需要收集和处理遍历结果。
在二叉树中的 DFS 示例
我们以二叉树的前序遍历为例,说明 DFS 的具体实现及其流程。
示例二叉树
1
/ \
2 3
/ \
4 5
前序遍历的步骤
前序遍历的顺序是:根节点 -> 左子树 -> 右子树。
递归实现:
const preorderTraversal = function(root) {
const res = []; // 用于存储结果
const dfs = (node) => {
if (!node) return; // 终止条件
res.push(node.val); // 处理当前节点
dfs(node.left); // 递归访问左子树
dfs(node.right); // 递归访问右子树
};
dfs(root); // 从根节点开始
return res; // 返回结果
};
迭代实现:
const preorderTraversal = function(root) {
const res = []; // 用于存储结果
const stk = []; // 初始化栈
if (root) stk.push(root); // 如果根节点不为空,压入栈
while (stk.length) {
const node = stk.pop(); // 弹出栈顶节点
res.push(node.val); // 处理当前节点
if (node.right) stk.push(node.right); // 先压入右子节点
if (node.left) stk.push(node.left); // 再压入左子节点(确保左子节点先被处理)
}
return res; // 返回结果
};
要计算二叉树的最大深度,我们可以使用深度优先搜索(DFS)的方法。下面我将详细分析这个问题的解题思路,包括代码实现和执行过程。
问题定义
最大深度是指从根节点到最远叶子节点的最长路径上的节点数。也就是说,我们需要找到从根节点到达叶子节点的路径中,节点的数量最多的那条路径。
解题思路
-
终止条件:
- 如果当前节点为空(即
root
为null
),则该路径的深度为0
。
- 如果当前节点为空(即
-
递归返回值:
- 如果节点不为空,递归计算其左子树和右子树的最大深度。
- 返回左右子树深度的最大值加
1
(表示当前节点本身)。
-
执行过程:
- 递归会一直向下遍历到叶子节点,然后开始回溯,计算每个节点的深度。
-
时间复杂度:
- 由于每个节点只会被访问一次,时间复杂度为
O(n)
,其中n
是树中节点的数量。
- 由于每个节点只会被访问一次,时间复杂度为
代码实现
下面是计算二叉树最大深度的 JavaScript 实现:
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function(root) {
// 终止条件:如果当前节点为空,返回深度 0
if (!root) {
return 0;
} else {
// 递归计算左右子树的深度
const left = maxDepth(root.left);
const right = maxDepth(root.right);
// 返回左右子树深度的最大值加 1
return Math.max(left, right) + 1;
}
};
执行过程示例
我们以以下二叉树为例,来演示 maxDepth
函数的执行过程:
1
/ \
2 3
/ \
4 5
递归调用过程
-
初始调用:
maxDepth(1)
,当前节点是1
。
-
访问左子树:
- 调用
maxDepth(2)
,当前节点是2
。
- 调用
-
继续访问左子树:
- 调用
maxDepth(4)
,当前节点是4
。 maxDepth(4.left)
和maxDepth(4.right)
都返回0
(叶子节点)。- 返回
Math.max(0, 0) + 1 = 1
(节点4
的深度为1
)。
- 调用
-
返回到节点
2
,访问右子树:- 调用
maxDepth(5)
,当前节点是5
。 maxDepth(5.left)
和maxDepth(5.right)
都返回0
(叶子节点)。- 返回
Math.max(0, 0) + 1 = 1
(节点5
的深度为1
)。
- 调用
-
返回到节点
2
:- 左子树深度为
1
(节点4
),右子树深度为1
(节点5
)。 - 返回
Math.max(1, 1) + 1 = 2
(节点2
的深度为2
)。
- 左子树深度为
-
返回到节点
1
,访问右子树:- 调用
maxDepth(3)
,当前节点是3
。 maxDepth(3.left)
和maxDepth(3.right)
都返回0
(叶子节点)。- 返回
Math.max(0, 0) + 1 = 1
(节点3
的深度为1
)。
- 调用
-
返回到节点
1
:- 左子树深度为
2
(节点2
),右子树深度为1
(节点3
)。 - 返回
Math.max(2, 1) + 1 = 3
(根节点1
的深度为3
)。
- 左子树深度为
最终结果
通过上述过程,得到的最大深度为 3
,即从根节点到最远叶子节点的最长路径包含 3
个节点(1 -> 2 -> 4
或 1 -> 2 -> 5
)。
BFS待定