"左小右大,二分查找的树形版本!" 🎯
📖 一、什么是二叉搜索树?从家族族谱说起
1.1 生活中的场景
想象你在查族谱找人:
普通链表查找:
张三 → 李四 → 王五 → 赵六 → 钱七 → ...
找"王五":
1. 从头开始
2. 逐个比较
3. 第3个才找到
时间:O(n) 😰
二叉搜索树查找:
李四(4)
/ \
张三(3) 王五(5)
/ \
赵六(6) 钱七(7)
找"王五":
1. 从根开始:李四(4),5 > 4,往右
2. 到达王五(5),找到!
时间:O(log n) ⚡
1.2 专业定义
二叉搜索树(Binary Search Tree, BST) 是一种特殊的二叉树,满足以下性质:
核心性质:
- ✅ 左子树的所有节点值 < 根节点值
- ✅ 右子树的所有节点值 > 根节点值
- ✅ 左右子树也都是二叉搜索树(递归定义)
时间复杂度:
- 平均:O(log n) - 查找、插入、删除
- 最坏:O(n) - 退化成链表时
🎨 二、二叉搜索树的结构
2.1 基本结构图解
8 ← 根节点
/ \
3 10 ← 第2层
/ \ \
1 6 14 ← 第3层
/ \ /
4 7 13 ← 第4层
性质验证:
- 左子树 {1,3,4,6,7} < 8
- 右子树 {10,13,14} > 8
- 递归:3的左子树{1} < 3,右子树{4,6,7} > 3
2.2 节点定义
class TreeNode {
int val; // 节点值
TreeNode left; // 左子节点
TreeNode right; // 右子节点
public TreeNode(int val) {
this.val = val;
this.left = null;
this.right = null;
}
}
2.3 错误示例(不是BST)
❌ 错误示例1:
8
/ \
3 10
/ \
1 12 ← 错!12 > 8,不应在左子树
❌ 错误示例2:
8
/ \
3 10
/
5 ← 错!5 < 8,不应在右子树
🔧 三、核心操作
3.1 查找操作(Search)
算法思路:
- 从根节点开始
- 如果目标值 = 当前节点值,找到!
- 如果目标值 < 当前节点值,往左子树找
- 如果目标值 > 当前节点值,往右子树找
- 重复2-4,直到找到或遇到null
递归实现:
public class BinarySearchTree {
private TreeNode root;
// 查找(递归)
public TreeNode search(int key) {
return searchRecursive(root, key);
}
private TreeNode searchRecursive(TreeNode node, int key) {
// 基本情况:空节点或找到
if (node == null || node.val == key) {
return node;
}
// 递归查找
if (key < node.val) {
return searchRecursive(node.left, key); // 左子树
} else {
return searchRecursive(node.right, key); // 右子树
}
}
// 查找(迭代)
public TreeNode searchIterative(int key) {
TreeNode current = root;
while (current != null) {
if (key == current.val) {
return current;
} else if (key < current.val) {
current = current.left;
} else {
current = current.right;
}
}
return null;
}
}
查找过程演示:
查找 6:
8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13
步骤:
1. 从8开始:6 < 8,往左
2. 到3:6 > 3,往右
3. 到6:找到!✅
查找次数:3次(树的高度)
3.2 插入操作(Insert)
算法思路:
- 如果树为空,新节点成为根节点
- 否则,从根节点开始比较:
- 如果新值 < 当前值,往左走
- 如果新值 > 当前值,往右走
- 找到空位置,插入新节点
代码实现:
// 插入(递归)
public void insert(int val) {
root = insertRecursive(root, val);
}
private TreeNode insertRecursive(TreeNode node, int val) {
// 找到空位置,创建新节点
if (node == null) {
return new TreeNode(val);
}
// 递归插入
if (val < node.val) {
node.left = insertRecursive(node.left, val);
} else if (val > node.val) {
node.right = insertRecursive(node.right, val);
}
// 如果val == node.val,不插入(避免重复)
return node;
}
// 插入(迭代)
public void insertIterative(int val) {
if (root == null) {
root = new TreeNode(val);
return;
}
TreeNode current = root;
TreeNode parent = null;
while (current != null) {
parent = current;
if (val < current.val) {
current = current.left;
} else if (val > current.val) {
current = current.right;
} else {
return; // 已存在,不插入
}
}
// parent是待插入位置的父节点
if (val < parent.val) {
parent.left = new TreeNode(val);
} else {
parent.right = new TreeNode(val);
}
}
插入过程演示:
原树:
8
/ \
3 10
插入 6:
步骤1:6 < 8,往左
步骤2:6 > 3,往右
步骤3:右边为空,插入
结果:
8
/ \
3 10
\
6 ← 新插入
3.3 删除操作(Delete)⭐ 最复杂
删除有3种情况:
情况1:删除叶子节点(无子节点)
最简单,直接删除
删除 1:
8 8
/ \ / \
3 10 → 3 10
/ \
1 6
情况2:删除只有一个子节点的节点
用子节点替换被删除节点
删除 10:
8 8
/ \ / \
3 10 → 3 14
/ \ \ / \
1 6 14 1 6
情况3:删除有两个子节点的节点 ⭐
找到右子树的最小值(或左子树的最大值)替换
删除 3:
步骤1:找右子树最小值(4)
步骤2:用4替换3
步骤3:删除原来的4
8 8
/ \ / \
3 10 → 4 10
/ \ \ / \ \
1 6 14 1 6 14
/ \ \
4 7 7
完整代码:
// 删除节点
public void delete(int val) {
root = deleteRecursive(root, val);
}
private TreeNode deleteRecursive(TreeNode node, int val) {
if (node == null) {
return null;
}
// 查找要删除的节点
if (val < node.val) {
node.left = deleteRecursive(node.left, val);
} else if (val > node.val) {
node.right = deleteRecursive(node.right, val);
} else {
// 找到了要删除的节点
// 情况1:叶子节点或只有一个子节点
if (node.left == null) {
return node.right;
} else if (node.right == null) {
return node.left;
}
// 情况2:有两个子节点
// 找右子树的最小值
TreeNode minNode = findMin(node.right);
// 用最小值替换当前节点的值
node.val = minNode.val;
// 删除右子树中的最小值节点
node.right = deleteRecursive(node.right, minNode.val);
}
return node;
}
// 找到子树的最小值节点
private TreeNode findMin(TreeNode node) {
while (node.left != null) {
node = node.left;
}
return node;
}
3.4 遍历操作
中序遍历(In-Order):左 → 根 → 右
特性:BST的中序遍历结果是有序的!
8
/ \
3 10
/ \ \
1 6 14
/ \ /
4 7 13
中序遍历:1, 3, 4, 6, 7, 8, 10, 13, 14 ← 有序!
代码:
// 中序遍历(递归)
public void inorderTraversal(TreeNode node) {
if (node == null) return;
inorderTraversal(node.left); // 左
System.out.print(node.val + " "); // 根
inorderTraversal(node.right); // 右
}
// 前序遍历:根 → 左 → 右
public void preorderTraversal(TreeNode node) {
if (node == null) return;
System.out.print(node.val + " "); // 根
preorderTraversal(node.left); // 左
preorderTraversal(node.right); // 右
}
// 后序遍历:左 → 右 → 根
public void postorderTraversal(TreeNode node) {
if (node == null) return;
postorderTraversal(node.left); // 左
postorderTraversal(node.right); // 右
System.out.print(node.val + " "); // 根
}
💻 四、完整BST实现
public class BinarySearchTree {
private TreeNode root;
// 节点类
static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
}
// 插入
public void insert(int val) {
root = insertRecursive(root, val);
}
private TreeNode insertRecursive(TreeNode node, int val) {
if (node == null) {
return new TreeNode(val);
}
if (val < node.val) {
node.left = insertRecursive(node.left, val);
} else if (val > node.val) {
node.right = insertRecursive(node.right, val);
}
return node;
}
// 查找
public boolean search(int val) {
return searchRecursive(root, val) != null;
}
private TreeNode searchRecursive(TreeNode node, int val) {
if (node == null || node.val == val) {
return node;
}
return val < node.val ?
searchRecursive(node.left, val) :
searchRecursive(node.right, val);
}
// 删除
public void delete(int val) {
root = deleteRecursive(root, val);
}
private TreeNode deleteRecursive(TreeNode node, int val) {
if (node == null) return null;
if (val < node.val) {
node.left = deleteRecursive(node.left, val);
} else if (val > node.val) {
node.right = deleteRecursive(node.right, val);
} else {
if (node.left == null) return node.right;
if (node.right == null) return node.left;
TreeNode minNode = findMin(node.right);
node.val = minNode.val;
node.right = deleteRecursive(node.right, minNode.val);
}
return node;
}
private TreeNode findMin(TreeNode node) {
while (node.left != null) {
node = node.left;
}
return node;
}
// 中序遍历
public void inorder() {
inorderRecursive(root);
System.out.println();
}
private void inorderRecursive(TreeNode node) {
if (node != null) {
inorderRecursive(node.left);
System.out.print(node.val + " ");
inorderRecursive(node.right);
}
}
// 获取最小值
public int findMinValue() {
if (root == null) throw new IllegalStateException("Tree is empty");
return findMin(root).val;
}
// 获取最大值
public int findMaxValue() {
if (root == null) throw new IllegalStateException("Tree is empty");
TreeNode node = root;
while (node.right != null) {
node = node.right;
}
return node.val;
}
// 测试
public static void main(String[] args) {
BinarySearchTree bst = new BinarySearchTree();
// 插入数据
int[] values = {8, 3, 10, 1, 6, 14, 4, 7, 13};
System.out.println("=== 插入数据 ===");
for (int val : values) {
bst.insert(val);
System.out.print(val + " ");
}
System.out.println();
// 中序遍历(有序)
System.out.println("\n=== 中序遍历(有序)===");
bst.inorder();
// 查找
System.out.println("\n=== 查找 ===");
System.out.println("查找6: " + bst.search(6));
System.out.println("查找15: " + bst.search(15));
// 最值
System.out.println("\n=== 最值 ===");
System.out.println("最小值: " + bst.findMinValue());
System.out.println("最大值: " + bst.findMaxValue());
// 删除
System.out.println("\n=== 删除节点3 ===");
bst.delete(3);
bst.inorder();
}
}
输出:
=== 插入数据 ===
8 3 10 1 6 14 4 7 13
=== 中序遍历(有序)===
1 3 4 6 7 8 10 13 14
=== 查找 ===
查找6: true
查找15: false
=== 最值 ===
最小值: 1
最大值: 14
=== 删除节点3 ===
1 4 6 7 8 10 13 14
🎯 五、应用场景
5.1 有序数据存储
// 学生成绩管理
class StudentGrade {
int score;
String name;
StudentGrade(int score, String name) {
this.score = score;
this.name = name;
}
}
// 按成绩排序的BST
BinarySearchTree scoreTree = new BinarySearchTree();
scoreTree.insert(85); // 张三
scoreTree.insert(92); // 李四
scoreTree.insert(78); // 王五
// 中序遍历得到有序成绩
5.2 范围查询
// 查找[low, high]范围内的所有值
public List<Integer> rangeScan(int low, int high) {
List<Integer> result = new ArrayList<>();
rangeScanHelper(root, low, high, result);
return result;
}
private void rangeScanHelper(TreeNode node, int low, int high, List<Integer> result) {
if (node == null) return;
// 如果当前值 > low,左子树可能有符合条件的
if (node.val > low) {
rangeScanHelper(node.left, low, high, result);
}
// 如果当前值在范围内,加入结果
if (node.val >= low && node.val <= high) {
result.add(node.val);
}
// 如果当前值 < high,右子树可能有符合条件的
if (node.val < high) {
rangeScanHelper(node.right, low, high, result);
}
}
5.3 第K小/大元素
// 找第K小的元素
public int kthSmallest(TreeNode root, int k) {
List<Integer> list = new ArrayList<>();
inorderToList(root, list);
return list.get(k - 1);
}
private void inorderToList(TreeNode node, List<Integer> list) {
if (node == null) return;
inorderToList(node.left, list);
list.add(node.val);
inorderToList(node.right, list);
}
🎓 六、经典面试题
面试题1:验证BST
LeetCode 98
public boolean isValidBST(TreeNode root) {
return isValidBSTHelper(root, null, null);
}
private boolean isValidBSTHelper(TreeNode node, Integer min, Integer max) {
if (node == null) return true;
// 检查当前节点是否在(min, max)范围内
if ((min != null && node.val <= min) ||
(max != null && node.val >= max)) {
return false;
}
// 递归检查左右子树
return isValidBSTHelper(node.left, min, node.val) &&
isValidBSTHelper(node.right, node.val, max);
}
面试题2:BST转有序数组
public List<Integer> bstToArray(TreeNode root) {
List<Integer> result = new ArrayList<>();
inorder(root, result);
return result;
}
private void inorder(TreeNode node, List<Integer> result) {
if (node == null) return;
inorder(node.left, result);
result.add(node.val);
inorder(node.right, result);
}
面试题3:BST的最近公共祖先
LeetCode 235
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (p.val < root.val && q.val < root.val) {
return lowestCommonAncestor(root.left, p, q); // 都在左边
} else if (p.val > root.val && q.val > root.val) {
return lowestCommonAncestor(root.right, p, q); // 都在右边
} else {
return root; // 分叉点就是LCA
}
}
面试题4:BST的优缺点?
答案:
| 特性 | BST | 数组 | 链表 |
|---|---|---|---|
| 查找 | O(log n)平均 | O(log n)二分 | O(n) |
| 插入 | O(log n)平均 | O(n)移动 | O(1)头插 |
| 删除 | O(log n)平均 | O(n)移动 | O(n)查找 |
| 有序遍历 | O(n) | O(n) | O(n log n)排序 |
| 空间 | O(n) | O(n) | O(n) |
优点:
- ✅ 查找、插入、删除都是O(log n)
- ✅ 中序遍历得到有序序列
- ✅ 支持范围查询
缺点:
- ❌ 最坏情况退化成链表O(n)
- ❌ 不平衡时性能下降
- ❌ 需要额外的指针空间
面试题5:如何避免BST退化?
答案: 使用平衡二叉搜索树:
- AVL树:严格平衡,|左高度 - 右高度| ≤ 1
- 红黑树:近似平衡,保证O(log n)
- B树/B+树:多路平衡,数据库索引
🎪 七、趣味小故事
故事:图书馆的分类法
从前有个图书馆,书籍都堆在一起。
没有BST的日子:
管理员老张每天被累惨:
- 📚 找一本书要从头翻到尾
- 新书来了不知道放哪
- 查找时间:O(n) 😰
引入BST后:
老张设计了一套分类法:
[5000号书]
/ \
[3000] [7000]
/ \ / \
[2000] [4000] [6000] [8000]
规则:
- 编号 < 当前,放左边
- 编号 > 当前,放右边
现在找书:
找6500号书:
1. 从5000开始:6500 > 5000,往右
2. 到7000:6500 < 7000,往左
3. 到6000:找到附近!
只需3步,而不是遍历所有书!⚡
新书入库:
新书6200:
1. 从5000开始:6200 > 5000,往右
2. 到7000:6200 < 7000,往左
3. 到6000:6200 > 6000,往右
4. 右边为空,放这里!
老张笑了:"BST真是神器!查找插入都快!"
这就是BST的魔力——O(log n)的效率!🎯
📚 八、知识点总结
核心要点 ✨
- 定义:左小右大的二叉树
- 性质:中序遍历得到有序序列
- 操作:
- 查找:O(log n) 平均
- 插入:O(log n) 平均
- 删除:O(log n) 平均,3种情况
- 优势:查找、插入、删除都快
- 劣势:可能退化成链表
- 改进:平衡树(AVL、红黑树)
记忆口诀 🎵
二叉搜索树真巧,
左小右大要记牢。
查找插入都二分,
时间复杂对数好。
删除情况分三种,
叶子单子双子考。
中序遍历很神奇,
有序序列自然到。
退化问题要警惕,
平衡树来解烦恼!
操作总结表 📊
| 操作 | 平均时间 | 最坏时间 | 说明 |
|---|---|---|---|
| 查找 | O(log n) | O(n) | 二分查找 |
| 插入 | O(log n) | O(n) | 找到位置插入 |
| 删除 | O(log n) | O(n) | 3种情况 |
| 最小值 | O(log n) | O(n) | 最左节点 |
| 最大值 | O(log n) | O(n) | 最右节点 |
| 遍历 | O(n) | O(n) | 访问所有节点 |
🌟 九、总结彩蛋
恭喜你!🎉 你已经掌握了二叉搜索树这个重要的数据结构!
记住:
- 🌲 左小右大是核心
- ⚡ O(log n)是优势
- 🔄 中序遍历得有序
- ⚠️ 退化问题要注意
最后送你一张图
根
/ \
小 大
/ \ / \
更小 小大 更大
左小右大永不忘!
下次见,继续学习平衡树! 💪😄
📖 参考资料
- 《算法导论》第12章 - 二叉搜索树
- 《数据结构与算法分析》- BST
- LeetCode BST专题
- GeeksforGeeks - Binary Search Tree
作者: AI算法导师
最后更新: 2025年11月
难度等级: ⭐⭐⭐ (中级)
预计学习时间: 3-4小时
💡 温馨提示:理解BST是学习平衡树(AVL、红黑树)的基础,一定要掌握好!