二叉树的遍历框架总结
前序遍历框架如下:#
void traverse(TreeNode root) {
if (root == null) return;
// 前序遍历的代码
traverse(root.left);
traverse(root.right);
}
后序遍历的代码框架:#
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
/* 后序遍历代码的位置 */
/* 在这里处理当前节点 */
}
如果当前节点要做的事情需要通过左右子树的计算结果推导出来,就要用到后序遍历。
以 二叉搜索子树的最大键值和这一题为例:
想计算子树中 BST 的最大和,站在当前节点的视角,需要做什么呢?
1、我肯定得知道左右子树是不是合法的 BST,如果这俩儿子有一个不是 BST,以我为根的这棵树肯定不会是 BST,对吧。
2、如果左右子树都是合法的 BST,我得瞅瞅左右子树加上自己还是不是合法的 BST 了。因为按照 BST 的定义,当前节点的值应该大于左子树的最大值,小于右子树的最小值,否则就破坏了 BST 的性质。
3、因为题目要计算最大的节点之和,如果左右子树加上我自己还是一棵合法的 BST,也就是说以我为根的整棵树是一棵 BST,那我需要知道我们这棵 BST 的所有节点值之和是多少,方便和别的 BST 争个高下,对吧。
根据以上三点,站在当前节点的视角,需要知道以下具体信息:
1、左右子树是否是 BST。
2、左子树的最大值和右子树的最小值。
3、左右子树的节点值之和。
只有知道了这几个值,我们才能满足题目的要求,后面我们会想方设法计算这些值。
我们需要的这些变量都是可以通过后序遍历得到的。
你计算以 root
为根的二叉树的节点之和,是不是可以通过左右子树的和加上 root.val
计算出来?
你计算以 root
为根的二叉树的最大值/最小值,是不是可以通过左右子树的最大值/最小值和 root.val
比较出来?
你判断以 root
为根的二叉树是不是 BST,是不是得先判断左右子树是不是 BST?是不是还得看看左右子树的最大值和最小值?
所以用后序遍历会很方便
要尽可能避免递归函数中调用其他递归函数
层级遍历二叉树的代码框架如下:#
void traverse(TreeNode root) {
if (root == null) return;
// 初始化队列,将 root 加入队列
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode cur = q.poll();
/* 层级遍历代码位置 */
System.out.println(root.val);
/*****************/
if (cur.left != null) {
q.offer(cur.left);
}
if (cur.right != null) {
q.offer(cur.right);
}
}
}
经典题目#
二叉树的最近公共祖先#
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
遇到任何递归型的问题,无非就是灵魂三问:
1、这个函数是干嘛的?
情况 1,如果
p
和q
都在以root
为根的树中,函数返回的即使p
和q
的最近公共祖先节点。情况 2,那如果
p
和q
都不在以root
为根的树中怎么办呢?函数理所当然地返回null
呗。情况 3,那如果
p
和q
只有一个存在于root
为根的树中呢?函数就会返回那个节点。
2、这个函数参数中的变量是什么的是什么?
函数参数中的变量是
root
,因为根据框架,lowestCommonAncestor(root)
会递归调用root.left
和root.right
;至于p
和q
,我们要求它俩的公共祖先,它俩肯定不会变化的。把「以
root
为根」转移成「以root
的子节点为根」,不断缩小问题规模
3、得到函数的递归结果,你应该干什么?
先想 base case,如果
root
为空,肯定得返回null
。如果root
本身就是p
或者q
,比如说root
就是p
节点吧,如果q
存在于以root
为根的树中,显然root
就是最近公共祖先;即使q
不存在于以root
为根的树中,按照情况 3 的定义,也应该返回root
节点。
以上两种情况的 base case 就可以把框架代码填充一点了:
TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 两种情况的 base case
if (root == null) return null;
if (root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
}
用递归调用的结果left
和right
来搞点事情。根据刚才第一个问题中对函数的定义,我们继续分情况讨论:
情况 1,如果
p
和q
都在以root
为根的树中,那么left
和right
一定分别是p
和q
(从 base case 看出来的)。情况 2,如果
p
和q
都不在以root
为根的树中,直接返回null
。情况 3,如果
p
和q
只有一个存在于root
为根的树中,函数返回该节点。
left
和right
非空,分别是p
和q
,可以说明root
是它们的公共祖先,但能确定root
就是「最近」公共祖先吗?
这就是一个巧妙的地方了,因为这里是二叉树的后序遍历啊!前序遍历可以理解为是从上往下,而后序遍历是从下往上,就好比从
p
和q
出发往上走,第一次相交的节点就是这个root
,所以这个root
当然是最近公共祖先了
public class LowestCommonAncestor {
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// base case
if(root == null) {
return null;
}
// 如果遍历到的根节点为p或者q,说明在父节点的子树下找到了该节点,所以返回该父节点
if(p==root||q==root) {
return root;
}
// 后序遍历,递归调用,left是在父节点的左子树中找到的p节点或者q节点或者空节点,right同理
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
// 如果左子树和右子树都找到了p,q节点,说明父节点就是最近公共祖先,因为是后序遍历,所以能确保该父节点就是最近的祖先
if(left!=null&&right!=null) {
return root;
}
// 如果左子树和右子树都没有找到p,q节点,说明p,q节点和该父节点没关系,返回null,表示该父节点的子树下不包含p,q节点
if(left==null&&right==null) {
return null;
}
// 在上边条件都不满足的情况下,说明在该父节点下只找到了p或者q其中一个节点,返回这个找到的节点即可,
// 如果是最外层的递归调用可能是因为另一个节点在这个节点的子树下,已经遍历找到了这个节点,就直接返回了没有再去找另一个节点,或者是两个节点都在左子树的某一个父节点下,所以右子树没有找到节点,这里当然返回这两个节点所在树的子调用里的父节点也就是现在返回的left了,右子树同理
return left!=null?left:right;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~