二叉树与二叉搜索树:递归遍历三板斧
线性结构(数组、链表)的局限在于:它们只有”一维”的先后关系。
但很多现实问题天然是”层次”的——目录结构、组织架构、表达式、决策过程。这时候就需要树。
而二叉树,是所有树结构里最基础、也最重要的一种。
什么是二叉树
二叉树:每个节点最多有两个子节点(左子节点和右子节点)。
几个必须记住的术语:
- 根节点:最顶层的节点
- 叶子节点:没有子节点的节点
- 深度:从根到该节点的边数
- 高度:从该节点到最远叶子的边数
递归遍历三板斧
处理树的几乎所有问题,都离不开遍历。二叉树有三种经典的深度优先遍历,区别只在于访问根节点的时机:
1 | // 前序遍历:根 → 左 → 右 |
看出来了吗?三种遍历的代码结构一模一样,只是 visit(node) 的位置不同。
这三种遍历各有用途:
- 前序:适合”先处理父节点,再处理子节点”的场景,比如复制一棵树、序列化
- 中序:二叉搜索树的中序遍历,结果是有序的(这是最经典的性质)
- 后序:适合”先处理完子节点,再处理父节点”的场景,比如删除一棵树、计算子树的高度
递归为什么适合处理树
递归和树是天生一对,因为树本身就是递归定义的:一棵树 = 一个根节点 + 左子树 + 右子树,而左右子树本身又是树。
所以很多树的问题,都可以用一句话描述解法:
先处理根,再递归处理左右子树。
写树的递归代码,关键是确定”当前节点”要做什么,然后相信递归会帮你处理子树。别试图在脑子里展开整个递归过程,那样会晕。
二叉搜索树(BST)
普通的二叉树没什么规律,查找只能一个个遍历。二叉搜索树加了一条规则:
左子树所有节点 < 根 < 右子树所有节点。
有了这条规则,查找就变成”二分”了:
1 | TreeNode search(TreeNode node, int key) { |
每次比较都能排除一半,所以平衡的 BST 查找是 O(log n)。
BST 的增删查都是 O(log n)(平衡情况下),这就是为什么它比链表(O(n))快得多。
BST 的致命弱点
BST 有个致命问题:如果插入的数据是有序的(比如 1, 2, 3, 4, 5),它会退化成一条链表,查找又变回 O(n)。
为了解决这个问题,才有了各种”自平衡”的树:
- AVL 树:严格平衡,插入删除旋转次数多,适合读多写少
- 红黑树:弱平衡,性能更均衡,Java 的 TreeMap、HashMap 的红黑树都是它
- B+ 树:多叉平衡树,适合磁盘存储,MySQL 索引就是它
所以你看,数据结构是一步步演进出来的:二叉树 → 二叉搜索树 → 平衡树 → B+ 树,每一步都在解决上一步的痛点。
小结
- 二叉树:树结构的地基,每个节点最多两个子节点
- 三种遍历:前序、中序、后序,区别只在”访问根的时机”
- 递归是处理树的核心武器,因为树天然是递归结构
- 二叉搜索树靠”左小右大”实现 O(log n) 查找,但有退化风险
- 平衡树(红黑树、B+ 树)是解决退化问题的演进
下一篇讲堆——它其实是”用数组实现的完全二叉树”。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 高晓庄 札记!
