一、什么是 BST?
二分搜索树(Binary Search Tree,BST) 是满足以下性质的二叉树:
- 左子树所有节点值 < 当前节点值
- 右子树所有节点值 > 当前节点值
- 左右子树也分别是 BST
Mermaid · 渲染中(下方为源码)
graph TD 8((8)) --> 3((3)) 8 --> 10((10)) 3 --> 1((1)) 3 --> 6((6)) 6 --> 4((4)) 6 --> 7((7)) 10 --> 14((14)) 14 --> 13((13))
中序遍历:1, 3, 4, 6, 7, 8, 10, 13, 14 ← 一定有序
二、性质
- 中序遍历得到升序序列。
- 查找/插入/删除在平均情况下 O(log n)。
- 最坏情况退化为链表(数据有序插入)→ O(n)。
三、基本操作
3.1 查找
private boolean search(TreeNode node, int val) {
if (node == null) return false;
if (val == node.val) return true;
return val < node.val ? search(node.left, val) : search(node.right, val);
}3.2 插入
private TreeNode insert(TreeNode node, int val) {
if (node == null) return new TreeNode(val);
if (val < node.val) node.left = insert(node.left, val);
else if (val > node.val) node.right = insert(node.right, val);
return node; // 注意:重复值不插入(取决于业务)
}3.3 删除(最复杂)
分三种情况:
| 情况 | 操作 |
|---|---|
| 叶子节点 | 直接删 |
| 只有一个孩子 | 孩子替代 |
| 有两个孩子 | 用中序后继(右子树最小)或前驱(左子树最大)替代 |
private TreeNode delete(TreeNode node, int val) {
if (node == null) return null;
if (val < node.val) node.left = delete(node.left, val);
else if (val > node.val) node.right = delete(node.right, val);
else {
if (node.left == null) return node.right;
if (node.right == null) return node.left;
// 两个孩子:用中序后继(右子树最小)替代
TreeNode successor = findMin(node.right);
successor.right = deleteMin(node.right);
successor.left = node.left;
return successor;
}
return node;
}
private TreeNode findMin(TreeNode node) {
while (node.left != null) node = node.left;
return node;
}
private TreeNode deleteMin(TreeNode node) {
if (node.left == null) return node.right;
node.left = deleteMin(node.left);
return node;
}四、遍历方式
4.1 深度优先
// 前序:根 → 左 → 右
void preOrder(TreeNode n) {
if (n == null) return;
visit(n);
preOrder(n.left);
preOrder(n.right);
}
// 中序:左 → 根 → 右 → BST 得到升序
void inOrder(TreeNode n) {
if (n == null) return;
inOrder(n.left);
visit(n);
inOrder(n.right);
}
// 后序:左 → 右 → 根 → 子树信息收集
void postOrder(TreeNode n) {
if (n == null) return;
postOrder(n.left);
postOrder(n.right);
visit(n);
}4.2 广度优先(层序)
void levelOrder(TreeNode root) {
if (root == null) return;
Deque<TreeNode> q = new ArrayDeque<>();
q.offer(root);
while (!q.isEmpty()) {
TreeNode u = q.poll();
visit(u);
if (u.left != null) q.offer(u.left);
if (u.right != null) q.offer(u.right);
}
}五、BST 的高级操作
5.1 第 K 小元素
// 方法 1:中序遍历,计数器
int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
while (cur != null) { stack.push(cur); cur = cur.left; }
cur = stack.pop();
if (--k == 0) return cur.val;
cur = cur.right;
}
return -1;
}5.2 验证 BST
boolean isValidBST(TreeNode root) {
return validate(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
boolean validate(TreeNode n, long min, long max) {
if (n == null) return true;
if (n.val <= min || n.val >= max) return false;
return validate(n.left, min, n.val) && validate(n.right, n.val, max);
}注意:单纯比较父与子不够,要比较每个节点和它所有祖先的约束。
5.3 BST 范围和
求 BST 中 L ≤ x ≤ R 的节点值之和:
int rangeSumBST(TreeNode root, int L, int R) {
if (root == null) return 0;
if (root.val < L) return rangeSumBST(root.right, L, R);
if (root.val > R) return rangeSumBST(root.left, L, R);
return root.val + rangeSumBST(root.left, L, R) + rangeSumBST(root.right, L, R);
}利用 BST 的有序性可以剪枝,跳过整棵子树。
5.4 BST 转累加树
每个节点 = 自身 + 所有大于自身的节点值之和:
int sum = 0;
TreeNode convertBST(TreeNode root) {
if (root != null) {
convertBST(root.right); // 右 → 中 → 左(中序倒序)
sum += root.val;
root.val = sum;
convertBST(root.left);
}
return root;
}六、BST 的退化问题
6.1 退化成链表
依次插入 1, 2, 3, 4, 5:
1
\
2
\
3
\
...
树高 = n,操作退化为 O(n)。
6.2 解决方案
| 方案 | 特点 |
|---|---|
| AVL 树 | 严格平衡,查询更快,插入删除旋转多 |
| 红黑树 | 近似平衡,工业标准(TreeMap / std::map) |
| Treap / Splay | 概率平衡,实现简单 |
| 跳表 | 替代方案,Redis ZSet 使用 |
| B 树 | 外存场景,数据库索引 |
七、BST vs 哈希表
| 维度 | BST | 哈希表 |
|---|---|---|
| 查找 | O(log n) | O(1) 平均 |
| 范围查询 | ✅ O(log n + k) | ❌ |
| 顺序输出 | ✅ 中序 O(n) | ❌ |
| 最值 | ✅ O(log n) | ❌ O(n) |
| 内存 | 指针开销 | 哈希桶 |
选 BST:需要顺序访问、范围查询、有序遍历。
选哈希表:只要快速查找。
八、代码实现(TreeMap / TreeSet)
基于红黑树实现,提供 O(log n) 的有序操作:
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "C"); map.put(1, "A"); map.put(2, "B");
map.firstKey(); // 1
map.lastKey(); // 3
map.ceilingKey(2); // 2 (≥ 2 的最小键)
map.floorKey(2); // 2 (≤ 2 的最大键)
map.subMap(1, 3); // {1, 2} (范围视图)九、复杂度分析
| 操作 | 平均 | 最坏 |
|---|---|---|
| 查找 | O(log n) | O(n) |
| 插入 | O(log n) | O(n) |
| 删除 | O(log n) | O(n) |
| 中序遍历 | O(n) | O(n) |
平衡树能保证最坏 O(log n)。
十、易错点
- 验证 BST 边界:用
(min, max)区间递归,不是简单比左右孩子。 - 删除两个孩子:用后继/前驱,别忘了递归删除被移动的节点。
- 重复值处理:BST 通常不存重复值,或者存到固定子树(需在文档中明确)。
- 空指针:叶子节点的子指针是 null,递归时注意判空。
- 平衡问题:写业务用现成的 TreeMap,不要自己写裸 BST。
十一、刷题清单
| 难度 | 题目 | 关键 |
|---|---|---|
| 🟢 | 验证 BST | 区间递归 |
| 🟢 | BST 第 K 小元素 | 中序遍历 |
| 🟡 | BST 范围和 | 剪枝 |
| 🟡 | 把 BST 转累加树 | 逆中序 |
| 🟡 | 二叉搜索树迭代器 | 栈模拟中序 |
| 🟠 | 不同的 BST II | 递归枚举 |
| 🟠 | BST 中第 K 大的元素 | 迭代逆中序 |
| 🔴 | 恢复 BST(两个节点错位) | 中序 + 扫描 |
十二、心法
BST 的核心是"中序有序"。
任何 BST 问题,先想中序遍历能解决一半。
需要范围、最值、排名 → BST。
平衡的 BST 才能保证 O(log n),所以生产用红黑树(TreeMap)。