引言
二叉树(Binary Tree)作为算法领域的核心数据结构,在搜索、排序、数据库索引、编译器语法树构建等众多场景中都有着广泛应用。无论是初学者夯实算法基础,还是求职者备战技术面试,掌握二叉树相关算法都是不可或缺的。本文将通过 Java 语言,从基础概念、核心遍历算法出发,深入解析高频面试题,并分享进阶技巧,帮助开发者构建系统的二叉树算法知识体系。
一、二叉树基础概念
1.1 节点定义
在 Java 中,二叉树的节点通常定义如下:
-
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; -
} -
}
AI写代码
上述代码定义了TreeNode类,每个节点包含一个值val,以及指向左子节点left和右子节点right的引用,同时提供了不同的构造函数方便节点创建。
1.2 二叉树类型
| 类型 | 特征 | 应用场景 |
|---|---|---|
| 满二叉树 | 所有非叶子节点都有两个子节点,每一层节点数都达到最大值 | 构建完美平衡结构,用于理论研究或特定算法场景 |
| 完全二叉树 | 除最后一层外全满,最后一层左对齐,可通过数组高效存储 | 堆结构实现,如优先队列 |
| 二叉搜索树 (BST) | 左子树所有节点值 < 根 < 右子树,中序遍历可得到有序序列 | 快速查找、插入和删除操作,用于实现字典、符号表 |
| 平衡二叉树 (AVL) | 任意节点左右子树高度差≤1,通过旋转操作保持平衡 | 数据库索引、文件系统目录结构,保证查找效率稳定 |
| 红黑树 | 自平衡二叉搜索树,满足着色规则(节点为红或黑,根节点为黑等) | Java 中的TreeMap和TreeSet实现,在动态数据集合中有较好性能 |
二、核心遍历算法
2.1 递归遍历
递归遍历是实现二叉树遍历的直观方式,基于深度优先搜索(DFS)思想:
-
// 前序遍历:根 -> 左 -> 右 -
void preOrder(TreeNode root) { -
if (root == null) return; -
System.out.print(root.val + " "); -
preOrder(root.left); -
preOrder(root.right); -
} -
// 中序遍历:左 -> 根 -> 右(BST得到有序序列) -
void inOrder(TreeNode root) { -
if (root == null) return; -
inOrder(root.left); -
System.out.print(root.val + " "); -
inOrder(root.right); -
} -
// 后序遍历:左 -> 右 -> 根 -
void postOrder(TreeNode root) { -
if (root == null) return; -
postOrder(root.left); -
postOrder(root.right); -
System.out.print(root.val + " "); -
}
AI写代码
递归遍历简洁易懂,但当树的深度较大时,可能会导致栈溢出问题。
2.2 迭代遍历(栈实现)
使用栈可以将递归过程转化为迭代过程,避免栈溢出:
-
// 前序遍历(栈实现) -
List<Integer> preOrderTraversal(TreeNode root) { -
List<Integer> res = new ArrayList<>(); -
Deque<TreeNode> stack = new LinkedList<>(); -
TreeNode cur = root; -
while (cur != null || !stack.isEmpty()) { -
while (cur != null) { -
res.add(cur.val); // 访问根节点 -
stack.push(cur); -
cur = cur.left; // 深入左子树 -
} -
cur = stack.pop(); -
cur = cur.right; // 转向右子树 -
} -
return res; -
} -
// 中序遍历(栈实现) -
List<Integer> inOrderTraversal(TreeNode root) { -
List<Integer> res = new ArrayList<>(); -
Deque<TreeNode> stack = new LinkedList<>(); -
TreeNode cur = root; -
while (cur != null ||!stack.isEmpty()) { -
while (cur != null) { -
stack.push(cur); -
cur = cur.left; -
} -
cur = stack.pop(); -
res.add(cur.val); -
cur = cur.right; -
} -
return res; -
} -
// 后序遍历(栈实现) -
List<Integer> postOrderTraversal(TreeNode root) { -
List<Integer> res = new ArrayList<>(); -
Deque<TreeNode> stack1 = new LinkedList<>(); -
Deque<Integer> stack2 = new LinkedList<>(); -
TreeNode cur = root; -
while (cur != null ||!stack1.isEmpty()) { -
while (cur != null) { -
stack1.push(cur); -
stack2.push(1); -
cur = cur.right; -
} -
cur = stack1.pop(); -
if (stack2.pop() == 1) { -
stack1.push(cur); -
stack2.push(2); -
cur = cur.left; -
} else { -
res.add(cur.val); -
} -
} -
return res; -
}
AI写代码
2.3 层次遍历(队列实现)
层次遍历基于广度优先搜索(BFS),使用队列来实现:
-
List<List<Integer>> levelOrder(TreeNode root) { -
List<List<Integer>> res = new ArrayList<>(); -
if (root == null) return res; -
Queue<TreeNode> queue = new LinkedList<>(); -
queue.offer(root); -
while (!queue.isEmpty()) { -
int levelSize = queue.size(); -
List<Integer> level = new ArrayList<>(); -
for (int i = 0; i < levelSize; i++) { -
TreeNode node = queue.poll(); -
level.add(node.val); -
if (node.left != null) queue.offer(node.left); -
if (node.right != null) queue.offer(node.right); -
} -
res.add(level); -
} -
return res; -
}

AI写代码
层次遍历常用于获取二叉树的每一层节点值,或判断树的某些性质,如是否为完全二叉树。
三、高频面试题精讲
3.1 二叉树的最大深度(LeetCode 104)
题目描述:给定一个二叉树,找出其最大深度。
-
int maxDepth(TreeNode root) { -
if (root == null) return 0; -
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; -
}
AI写代码
解题思路:使用递归方法,分别计算左子树和右子树的最大深度,取较大值再加上根节点这一层。
3.2 对称二叉树(LeetCode 101)
题目描述:给定一个二叉树,检查它是否是镜像对称的。
-
boolean isSymmetric(TreeNode root) { -
return root == null || checkSymmetric(root.left, root.right); -
} -
boolean checkSymmetric(TreeNode left, TreeNode right) { -
if (left == null && right == null) return true; -
if (left == null || right == null) return false; -
return left.val == right.val -
&& checkSymmetric(left.left, right.right) -
&& checkSymmetric(left.right, right.left); -
}
AI写代码
解题思路:递归比较左子树的左节点和右子树的右节点,以及左子树的右节点和右子树的左节点。
3.3 二叉树的序列化(LeetCode 297)
题目描述:设计一个算法来序列化和反序列化二叉树。
-
public String serialize(TreeNode root) { -
if (root == null) return "null"; -
return root.val + "," + serialize(root.left) + "," + serialize(root.right); -
} -
public TreeNode deserialize(String data) { -
Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(","))); -
return buildTree(queue); -
} -
private TreeNode buildTree(Queue<String> queue) { -
String val = queue.poll(); -
if ("null".equals(val)) return null; -
TreeNode node = new TreeNode(Integer.parseInt(val)); -
node.left = buildTree(queue); -
node.right = buildTree(queue); -
return node; -
}
AI写代码
解题思路:序列化时使用前序遍历将二叉树转化为字符串,反序列化时根据字符串重新构建二叉树。
四、进阶算法技巧
4.1 Morris 遍历
Morris 遍历是一种实现 O (1) 空间复杂度中序遍历的方法,其核心思想是利用叶子节点的空指针来保存前驱节点信息,从而避免使用栈。该算法通过在遍历过程中构建临时的线索二叉树,实现对树的高效遍历,具体步骤较为复杂,但在对空间要求苛刻的场景下非常有用。
4.2 二叉搜索树验证(LeetCode 98)
题目描述:给定一个二叉树,判断其是否是一个有效的二叉搜索树。
-
boolean isValidBST(TreeNode root) { -
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE); -
} -
boolean validate(TreeNode node, long lower, long upper) { -
if (node == null) return true; -
if (node.val <= lower || node.val >= upper) return false; -
return validate(node.left, lower, node.val) -
&& validate(node.right, node.val, upper); -
}
AI写代码
解题思路:递归验证每个节点的值是否在其对应的取值范围内,左子树所有节点值小于根节点,右子树所有节点值大于根节点。
五、注意事项
- 空指针处理:在操作二叉树节点前,始终要检查节点是否为
null,避免出现NullPointerException。 - 递归终止条件:明确递归的退出条件,防止无限递归导致栈溢出。
- 栈溢出风险:当二叉树深度过大时,递归遍历可能会耗尽栈空间,此时应优先使用迭代法。
- 状态保存:在使用回溯算法解决二叉树问题时,要及时恢复现场,避免影响后续操作。
六、性能优化方向
- 记忆化搜索:对于一些需要重复计算的问题,如计算二叉树中满足特定条件的路径和,使用记忆化搜索可以避免重复计算,提高效率。
- 尾递归优化:虽然 Java 目前对尾递归优化支持有限,但在一些特定编译器或运行环境中,可以利用尾递归优化来减少栈空间的占用。
- 迭代替代递归:将递归算法转化为迭代算法,通过显式使用栈或队列,可以有效降低空间复杂度。
- 剪枝策略:在解决一些搜索问题时,提前判断某些分支是否无效,及时终止搜索,减少不必要的计算。
结语
掌握二叉树算法的关键在于理解树形结构的递归本质,并熟练运用各种遍历框架来解决各类问题。从基础的遍历操作到复杂的算法应用,都需要通过大量的练习来加深理解。建议读者从本文的基础代码和题目入手,逐步挑战 LeetCode 上更多经典题目,在实践中实现从量变到质变的提升。
如果在学习过程中有任何疑问,欢迎在评论区留言交流,也希望大家分享自己的学习心得和技巧,共同进步!
原文地址:

被折叠的 条评论
为什么被折叠?



