1. 二叉树基础概念与核心特性
二叉树是每个节点最多有两个子节点的树形数据结构,这两个子节点分别称为左子节点和右子节点。在算法领域,二叉树是最基础也是最重要的数据结构之一,几乎所有的树形结构问题最终都会转化为二叉树问题来处理。
1.1 二叉树的基本类型
满二叉树:如果一棵二叉树的每一层节点数都达到最大值,即第k层有2^(k-1)个节点,且所有叶子节点都在同一层,这样的二叉树称为满二叉树。满二叉树的特点是节点总数与树的高度呈指数关系。
完全二叉树:除了最底层外,其他各层的节点数都达到最大值,且最底层的节点都集中在左侧连续位置。这种结构在堆排序和优先队列中应用广泛,因为它可以高效地用数组表示而不需要指针。
二叉搜索树(BST):一种有序的二叉树结构,对于任意节点:
- 左子树所有节点的值小于当前节点值
- 右子树所有节点的值大于当前节点值
- 左右子树也必须是二叉搜索树
BST的平均查找时间复杂度为O(log n),但在最坏情况下(退化成链表)会变为O(n)。
平衡二叉搜索树(AVL树):在BST基础上增加了平衡条件,要求任意节点的左右子树高度差不超过1。通过旋转操作保持平衡,确保查找效率始终维持在O(log n)。
实际工程中,C++的map/set和Java的TreeMap/TreeSet底层都是红黑树(一种近似平衡的BST),而非严格的AVL树,因为红黑树在插入删除时需要的旋转操作更少。
1.2 二叉树的存储方式
链式存储:
struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} };这是最直观的表示方法,每个节点通过指针连接子节点。面试手写代码时务必注意指针初始化为nullptr。
顺序存储(数组表示): 对于完全二叉树,可以用数组紧凑存储。若父节点索引为i,则:
- 左子节点索引:2*i + 1
- 右子节点索引:2*i + 2
这种表示节省指针空间,适合堆结构。但非完全二叉树会浪费数组空间。
2. 二叉树的遍历方法论
2.1 深度优先遍历(DFS)
递归三要素:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归逻辑
前序遍历(中-左-右)
def preorder(root): if not root: return print(root.val) # 中 preorder(root.left) # 左 preorder(root.right) # 右中序遍历(左-中-右)
BST的中序遍历结果是有序数组,这是BST的重要性质。
后序遍历(左-右-中)
常用于计算子树性质,如二叉树的直径问题。
记忆技巧:遍历顺序指的是"中节点"的处理位置
2.2 迭代法实现DFS
递归的本质是栈,因此所有递归写法都可以改为迭代法。以前序遍历为例:
vector<int> preorderTraversal(TreeNode* root) { vector<int> res; stack<TreeNode*> st; if (root) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); st.pop(); res.push_back(node->val); if (node->right) st.push(node->right); // 右先入栈 if (node->left) st.push(node->left); // 左后入栈 } return res; }2.3 广度优先遍历(BFS)
使用队列实现层序遍历,可以计算二叉树的最小深度、右视图等问题:
List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); Queue<TreeNode> queue = new LinkedList<>(); if (root != null) queue.offer(root); while (!queue.isEmpty()) { int size = queue.size(); List<Integer> level = new ArrayList<>(); for (int i = 0; i < size; 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; }3. 二叉树经典问题解析
3.1 二叉树的最大深度
递归解法:
def maxDepth(root): if not root: return 0 return 1 + max(maxDepth(root.left), maxDepth(root.right))迭代解法(层序遍历):
int maxDepth(TreeNode* root) { queue<TreeNode*> q; if (root) q.push(root); int depth = 0; while (!q.empty()) { int size = q.size(); depth++; while (size--) { TreeNode* node = q.front(); q.pop(); if (node->left) q.push(node->left); if (node->right) q.push(node->right); } } return depth; }3.2 对称二叉树判断
public boolean isSymmetric(TreeNode root) { return root == null || check(root.left, root.right); } boolean check(TreeNode left, TreeNode right) { if (left == null && right == null) return true; if (left == null || right == null) return false; return left.val == right.val && check(left.left, right.right) && check(left.right, right.left); }3.3 路径总和问题
def hasPathSum(root, target): if not root: return False if not root.left and not root.right: return root.val == target return hasPathSum(root.left, target - root.val) or \ hasPathSum(root.right, target - root.val)4. 二叉搜索树专项
4.1 BST验证
bool isValidBST(TreeNode* root) { TreeNode* prev = nullptr; stack<TreeNode*> st; while (root || !st.empty()) { while (root) { st.push(root); root = root->left; } root = st.top(); st.pop(); if (prev && prev->val >= root->val) return false; prev = root; root = root->right; } return true; }4.2 BST插入操作
public TreeNode insertIntoBST(TreeNode root, int val) { if (root == null) return new TreeNode(val); if (val < root.val) { root.left = insertIntoBST(root.left, val); } else { root.right = insertIntoBST(root.right, val); } return root; }5. 二叉树构造问题
5.1 从前序与中序遍历序列构造二叉树
def buildTree(preorder, inorder): if not preorder: return None root_val = preorder[0] root = TreeNode(root_val) idx = inorder.index(root_val) root.left = buildTree(preorder[1:1+idx], inorder[:idx]) root.right = buildTree(preorder[1+idx:], inorder[idx+1:]) return root5.2 从后序与中序遍历序列构造二叉树
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { if (inorder.empty()) return nullptr; int root_val = postorder.back(); TreeNode* root = new TreeNode(root_val); auto it = find(inorder.begin(), inorder.end(), root_val); int left_size = distance(inorder.begin(), it); vector<int> left_in(inorder.begin(), it); vector<int> right_in(it + 1, inorder.end()); vector<int> left_post(postorder.begin(), postorder.begin() + left_size); vector<int> right_post(postorder.begin() + left_size, postorder.end() - 1); root->left = buildTree(left_in, left_post); root->right = buildTree(right_in, right_post); return root; }6. 二叉树进阶技巧
6.1 Morris遍历
一种空间复杂度O(1)的遍历方法,通过利用叶子节点的空指针实现:
public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<>(); TreeNode curr = root; while (curr != null) { if (curr.left == null) { res.add(curr.val); curr = curr.right; } else { TreeNode prev = curr.left; while (prev.right != null && prev.right != curr) { prev = prev.right; } if (prev.right == null) { prev.right = curr; curr = curr.left; } else { prev.right = null; res.add(curr.val); curr = curr.right; } } } return res; }6.2 序列化与反序列化
def serialize(root): if not root: return "null" return f"{root.val},{serialize(root.left)},{serialize(root.right)}" def deserialize(data): def helper(nodes): val = next(nodes) if val == "null": return None node = TreeNode(int(val)) node.left = helper(nodes) node.right = helper(nodes) return node return helper(iter(data.split(',')))7. 常见错误与调试技巧
- 空指针问题:递归时忘记检查root是否为null
- 遍历顺序混淆:前中后序代码相似,容易写混
- BST边界错误:处理BST时等号条件处理不当
- 递归栈溢出:树深度过大时可能引发栈溢出
- 修改原树结构:某些问题需要先复制树结构再操作
调试建议:
- 先画小规模树结构(3-5个节点)
- 使用print或debugger跟踪递归过程
- 对特殊case单独测试(空树、单节点、斜树等)
8. 二叉树题目训练路线
建议按照以下顺序刷题:
- 基础遍历(前中后序+层序)
- 简单属性判断(对称、平衡等)
- 路径相关问题
- 构造类问题
- BST专项
- 进阶问题(LCA、序列化等)
重点题目推荐:
- 二叉树的最大深度
- 平衡二叉树
- 二叉树中的最大路径和
- 二叉树的最近公共祖先
- 二叉树的序列化与反序列化