二叉树
在实际开发中,二叉树(尤其是它的各种优化变体,如红黑树、B/B+树、Trie树等)的应用无处不在。在真实的工业界,它们主要支撑了以下几个核心场景:
- 数据库与文件系统的底层存储引擎(B+树)
这是二叉树在工业界最伟大、最广泛的使用场景。
关系型数据库(如 MySQL):底层索引使用的是 B+树(多叉平衡搜索树)。它保证了数据查询的时间复杂度为 O(logN) ,并且由于树的高度极低,完美契合磁盘的块读取机制,极大减少了磁盘 I/O。
NoSQL 数据库(如 MongoDB):底层索引通常使用 B树。
文件系统(如 EXT4, NTFS):管理磁盘上的海量文件和目录,底层也是基于树形结构来快速定位文件。 - 内存中的高效数据检索(红黑树)
当数据完全在内存中时,我们需要一种既能快速查找,又能快速插入/删除的结构,红黑树(一种自平衡二叉搜索树) 是绝对的主力:
Java 集合框架:TreeMap、TreeSet 底层全是红黑树,用于在内存中维护一个有序的键值对集合。
Linux 内核:Linux 的进程调度(CFS调度器)、内存管理(VMA管理)、定时器管理等核心模块,大量使用红黑树来管理海量动态数据。
Nginx:底层使用红黑树来管理定时器事件和路由配置。 - 字符串处理与路由匹配(Trie 字典树)
Trie 树(前缀树) 是一种特殊的树,专门用来处理字符串:
搜索引擎的自动补全:你在搜索框输入"app",下拉框瞬间提示"apple"、"application",底层就是 Trie 树的前缀匹配。
网络路由(最长前缀匹配):路由器的路由表查找、IP 地址匹配,大量使用 Trie 树及其压缩变体(Patricia Tree)。
敏感词过滤/词频统计:在海量文本中快速判断某个词是否存在。 - 编译器与语法解析(抽象语法树 AST)
代码编译:当你写下 a = b + c 时,编译器(如 Java 的 javac,C++ 的 GCC)首先会将其解析成一棵抽象语法树(AST),然后再对这棵树进行遍历和优化,最后生成机器码。
前端工具链:Webpack、Babel、ESLint 等前端构建和代码检查工具,底层都是将 JavaScript 代码解析为 AST 树,然后通过遍历这棵树来进行代码转换或语法检查。 - 业务系统中的层级关系与表达式计算
组织架构与分类:公司部门层级、电商的商品多级分类、评论区的多级嵌套回复,在数据库中通常用树形结构(如邻接表、闭包表)来存储和查询。
表达式求值:数学公式 1 + 2 * 3 在计算时,会被解析成二叉树,通过后序遍历来保证运算优先级。
文章目录
-
- 二叉树
-
- 94.二叉树的中序遍历
- 104.二叉树的最大深度
- [226. 翻转二叉树](#226. 翻转二叉树)
- 101.对称二叉树
- [543. 二叉树的直径](#543. 二叉树的直径)
- 102.二叉树的层序遍历
- [108. 将有序数组转换为二叉搜索树](#108. 将有序数组转换为二叉搜索树)
- [98. 验证二叉搜索树](#98. 验证二叉搜索树)
- [230. 二叉搜索树中第 K 小的元素](#230. 二叉搜索树中第 K 小的元素)
- [199. 二叉树的右视图](#199. 二叉树的右视图)
- [114. 二叉树展开为链表](#114. 二叉树展开为链表)
- [105. 从前序与中序遍历序列构造二叉树](#105. 从前序与中序遍历序列构造二叉树)
- [437. 路径总和 III](#437. 路径总和 III)
- 236.二叉树的最近公共祖先
- [124. 二叉树中的最大路径和](#124. 二叉树中的最大路径和)
- 递归总结




94.二叉树的中序遍历
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inorder(root,res);
return res;
}
public void inorder(TreeNode root,List<Integer> res){
if(root == null)return;
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
104.二叉树的最大深度
递归
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root == null)return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left,right) + 1;
}
}
226. 翻转二叉树
先递归到底,再交换
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null)
return null;
invertTree(root.left);
invertTree(root.right);
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
return root;
}
}
101.对称二叉树
将整棵树的对称问题,转化为判断"左子树"和"右子树"是否互为镜像。通过 check 函数,每次递归都严格比较两个节点的值是否相等,然后让左节点的"左孩子"与右节点的"右孩子"对比,同时让左节点的"右孩子"与右节点的"左孩子"对比(即交叉比较),一路递归到底,只要所有交叉对应的节点都匹配,整棵树就是对称的
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root.left,root.right);
}
public boolean check(TreeNode left,TreeNode right){
//两边都为空 对称
if(left == null && right == null)return true;
//只有一边为空或者值不同 不对称
if(left == null || right == null || left.val != right.val)return false;
//继续向下交叉比较
return check(left.left,right.right) && check(left.right,right.left);
}
}
543. 二叉树的直径
遍历二叉树,在计算最大深度的同时,顺带把直径算出来
在当前节点拐点的直径长度 = 左子树的最大深度 + 右子树的最大深度
返回给父节点的是当前子树的最大深度= max(左子树的最大深度,右子树的最大深度)+1
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
maxDepth(root);
return res;
}
public int maxDepth(TreeNode root){
if(root == null)return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
res = Math.max(res,left + right);
return Math.max(left,right) + 1;
}
}
102.二叉树的层序遍历
BFS
- cur数组存当前正在遍历的节点
- nxt数组存被遍历节点的左右子节点
- vals数组存部分答案
- 遍历cur,把左右子节点记录到nxt中,同时把节点值记录到数组vals中,遍历结束后把vals加到答案里
- 遍历结束把cur替换成nxt,开始下一轮循环
- cur不为空就证明还没遍历完
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null)return List.of();
List<List<Integer>> ans = new ArrayList<>();
List<TreeNode> cur = List.of(root);
while(!cur.isEmpty()){
List<TreeNode> nxt = new ArrayList<>();
List<Integer> vals = new ArrayList<>(cur.size());
for(TreeNode node : cur){
vals.add(node.val);
if(node.left != null)nxt.add(node.left);
if (node.right != null) nxt.add(node.right);
}
cur = nxt;
ans.add(vals);
}
return ans;
}
}
优化一下,把cur数组和nxt数组用一个队列替代
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null)return List.of();
List<List<Integer>> ans = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
while(!q.isEmpty()){
int n = q.size();
List<Integer> vals = new ArrayList<>(n);
while(n > 0){
TreeNode node = q.poll();
vals.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
n--;
}
ans.add(vals);
}
return ans;
}
}
108. 将有序数组转换为二叉搜索树
平衡二叉搜索树:每个节点的左子树和右子树高度相差不超过1
由于给定的数组是严格升序的,要构建一棵高度平衡的二叉搜索树(BST),关键在于每次都选取当前区间的中间元素作为根节点 ,这样能保证左右子树的节点数量尽可能相等;随后,以中间元素为界,将数组一分为二,递归地对左半区间构建左子树、对右半区间构建右子树,直到区间越界(left > right)时返回 null 作为递归出口,最终自底向上拼接出一棵完美的平衡二叉搜索树。
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return build(nums,0,nums.length-1);
}
public TreeNode build(int[] nums,int left,int right){
if(left > right)return null;
int mid = (left + right)/2;
TreeNode root = new TreeNode(nums[mid]);
root.left = build(nums,left,mid-1);
root.right = build(nums,mid +1,right);
return root;
}
}
98. 验证二叉搜索树
- 前序遍历:先判断再递归
- 中序遍历:大于上一个节点
- 后序遍历:先递归再判断
递归时除了要传当前节点,还要传开区间的范围

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public boolean isValidBST(TreeNode root) {
return check(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean check(TreeNode root,long min,long max){
if(root == null)return true;
int temp = root.val;
if(temp <= min || temp >= max)return false;
return check(root.left,min,temp) && check(root.right,temp,max);
}
}
230. 二叉搜索树中第 K 小的元素
中序遍历 一棵二叉搜索树,输出的结果是一个从小到大排好序的数组
非递归法
用栈实现
利用栈完美模拟了二叉搜索树的中序遍历(左-根-右)过程:首先通过一个内层循环"一路向左" ,将途径的所有节点依次压入栈中直到最底端;接着从栈顶弹出节点 (此时弹出的即为当前树中的最小值),将其访问计数器加 1,若计数等于
k则直接返回该节点的值;最后将指针转向该节点的右子树 ,并在外层循环中重复上述"向左压栈、弹栈计数、转向右子树"的过程,直到精准命中第k小的元素
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
int cnt = 0;
while(curr != null || !stack.isEmpty()){
//遇到节点就先压栈,一直往左走,直到走到最左下角的叶子节点
while(curr != null){
stack.push(curr);
curr = curr.left;
}
//弹栈访问得到最小的节点
curr = stack.pop();
cnt++;
if(cnt == k)return curr.val;
//转向右子树
curr = curr.right;
}
return -1;
}
}
递归法
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int count = 0;
private int result = -1;
public int kthSmallest(TreeNode root, int k) {
inorder(root,k);
return result;
}
public void inorder(TreeNode node,int k){
if(node == null||result != -1)return;
inorder(node.left,k);
count++;
if(count == k){
result = node.val;
return;
}
inorder(node.right,k);
}
}
199. 二叉树的右视图

一层一层遍历,取每层最后一个数,就是右边能看到的

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if (root == null) {
return List.of();
}
List<Integer> res = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
while (!q.isEmpty()) {
int levelSize = q.size();
TreeNode rightNode = null;
for (int i = 0; i < levelSize; i++) {
TreeNode cur = q.poll();
if (i == levelSize - 1)
res.add(cur.val);
if (cur.left != null)
q.add(cur.left);
if (cur.right != null)
q.add(cur.right);
}
}
return res;
}
}
114. 二叉树展开为链表
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode res;
public void flatten(TreeNode root) {
if(root == null)return;
//开一个列表 用来存前序遍历的节点顺序
List<TreeNode> list = new ArrayList<>();
inorder(root,list);
//遍历列表,重新把节点串成链表
for(int i=0;i<list.size()-1;i++){
TreeNode cur = list.get(i);
TreeNode next = list.get(i+1);
cur.left = null;
cur.right = next;
}
}
public void inorder(TreeNode node,List<TreeNode> list){
if(node == null)return;
list.add(node);
inorder(node.left,list);
inorder(node.right,list);
}
}
优化
首先通过递归将当前节点的左右子树分别展平为链表,接着暂存右链表,将左链表整体移到右指针上并置空左指针,最后顺着移过来的左链表一路走到最右端,将暂存的右链表拼接在末尾,从而完成当前节点的展平
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public void flatten(TreeNode root) {
if(root == null)return;
flatten(root.left);//把左子树展平
flatten(root.right);//把右子树展平
TreeNode temp = root.right;
root.right = root.left;
root.left = null;
TreeNode cur = root;
while(cur.right != null)cur = cur.right;//走到原左子树的最后
cur.right = temp;//把原右子树挂到最后
}
}
105. 从前序与中序遍历序列构造二叉树
从前序遍历中找到根节点,用这个根节点取中序遍历中分开左右子树,接着继续递归分开每部分左右子树
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
Map<Integer,Integer> inorderMap = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++){
inorderMap.put(inorder[i],i);
}
return build(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
public TreeNode build(int[] preorder,int[] inorder,int preStart,int preEnd,int inStart,int inEnd){
if(preStart > preEnd || inStart > inEnd){
return null;
}
//取先序遍历初节点作为根节点
TreeNode node = new TreeNode(preorder[preStart]);
//根据根节点去中序遍历里,找到左右子树的范围
int index = inorderMap.get(node.val);
int leftPreStart = preStart + 1;
int leftPreEnd = preStart + (index - inStart);
int leftInStart = inStart;
int leftInEnd = index - 1;
node.left = build(preorder,inorder,leftPreStart,leftPreEnd,leftInStart,leftInEnd);
int rightPreStart = preStart + 1 + (index - inStart);
int rightPreEnd = preStart + (index - inStart) + (inEnd -index);
int rightInStart = index + 1;
int rightInEnd = inEnd;
node.right = build(preorder,inorder,rightPreStart,rightPreEnd,rightInStart,rightInEnd);
return node;
}
}
437. 路径总和 III
- 枚举所有路径的起点
- 计算以某个节点为起点的满足条件的路径的个数
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if(root == null)return 0;
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
int total = 0;
while(!q.isEmpty()){
TreeNode node = q.poll();
total += dfs(node,0,targetSum);
if(node.left != null)q.add(node.left);
if(node.right != null)q.add(node.right);
}
return total;
}
public int dfs(TreeNode node,long cur_sum,int targetSum){
if(node == null)return 0;
cur_sum += node.val;
int count = 0;
if(cur_sum == targetSum)count++;
count += dfs(node.left,cur_sum,targetSum);
count += dfs(node.right,cur_sum,targetSum);
return count;
}
}
优化
前缀和+递归+回溯
从根节点出发,每走一步就计算前缀和并"查账"看能否凑出目标值,然后"记账"并继续向下探索;探索完当前分支返回时,必须手动"擦账"以隔离不同分支,而前缀和的值则依靠递归栈自动回退
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
int ans = 0;
public int pathSum(TreeNode root, int targetSum) {
Map<Long,Integer> map = new HashMap<>();
map.put(0L,1);
dfs(root,map,0,targetSum);
return ans;
}
public void dfs(TreeNode root,Map<Long,Integer> map,long cur,int targetSum){
if(root == null)return;
cur += root.val;
ans += map.getOrDefault(cur-targetSum,0);
map.put(cur,map.getOrDefault(cur,0)+1);
dfs(root.left,map,cur,targetSum);
dfs(root.right,map,cur,targetSum);
map.put(cur,map.getOrDefault(cur,0)-1);
return;
}
}
236.二叉树的最近公共祖先

java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null || root == p || root == q){
return root;//找到p或q就不往下递归了
}
TreeNode left = lowestCommonAncestor(root.left,p,q);
TreeNode right = lowestCommonAncestor(root.right,p,q);
if(left != null && right != null)return root;//左右都找到,当前节点是最近公共祖先
//只有左子树找到,返回左子树的返回值
//只有右子树找到,返回右子树的返回值
//左右子树都没找到,返回null
return left != null ? left : right;
}
}
124. 二叉树中的最大路径和
- 算全局答案时:左 + 根 + 右(路径分叉,到此为止)。
- 算返回值时:根 + max(左, 右)(路径不分叉,继续向上)
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
private int res;
public int maxPathSum(TreeNode root) {
res = root.val;
dfs(root);
return res;
}
public int dfs(TreeNode root){
if(root == null){
return 0;
}
int lmax = Math.max(0,dfs(root.left));
int rmax = Math.max(0,dfs(root.right));
res = Math.max(res,lmax + rmax + root.val);
return root.val + Math.max(lmax,rmax);
}
}
递归总结
由于子问题的规模比原问题小,不断递下去,总会有个尽头
即递归的边界条件,直接返回它的答案
递归return的时候,怎么知道return到哪里的?
return 能精准返回,是因为 JVM 底层维护了一个"调用栈":每次调用函数时,系统都会压入一张"便签纸",记录下当前的局部变量和返回地址(执行到了哪一行) ;当函数执行 return 时,系统会弹出这张便签纸,并顺着上面记录的返回地址,将计算结果精准送回上一层调用处,继续往下执行