从满二叉树到删除节点,带你用“人话”彻底搞懂 BST 的核心逻辑!
🌳 一、引子:你见过这么有原则的树吗?
想象一下,有一棵树,它不仅长得整齐(比如满二叉树那种 节点的完美身材),还特别“讲规矩”:
- 左边的所有子孙都比它小;
- 右边的所有后代都比它大;
- 它自己站在中间,像个裁判,左右分明,绝不含糊。
这棵树,就是我们今天要聊的主角——二叉搜索树(Binary Search Tree, 简称 BST) 。
它不是普通的树,它是“排序界的强迫症患者”,是“查找算法里的效率担当”!
🔍 二、BST 是啥?一句话定义
二叉搜索树 = 空树 or (根 + 左子树 < 根 < 右子树,且左右子树也都是 BST)
听起来有点绕?没关系,我们用人话翻译:
- 如果一棵树是空的,那它当然是 BST(毕竟没人捣乱嘛)。
- 如果不是空的,那它的左孩子必须全比我小,右孩子必须全比我大,而且它的左右孩子也得遵守这个规则。
举个栗子🌰:
6
/ \
3 8
/ \ / \
1 4 7 9
- 6 的左边全是 <6 的(3,1,4),右边全是 >6 的(8,7,9)✅
- 3 的左边是 1(<3),右边是 4(>3)✅
- 8 的左边是 7(<8),右边是 9(>8)✅
所以,这是一棵合格的 BST!
💡 注意:BST 不要求“完全平衡”,只要满足大小关系就行。最坏情况下,它可能退化成一条链(比如插入 1,2,3,4,5…),这时候就变成 O(n) 的“线性查找”了,惨不忍睹 😭
⚡ 三、BST 的三大绝技:查、插、删
BST 的魅力,在于它能高效支持三种操作:查找、插入、删除。平均时间复杂度都是 O(log n) —— 比起数组的 O(n),简直是飞一般的感觉!
1️⃣ 查找:像玩“猜数字”游戏
function search(root, n) {
if (!root) return null;
if (root.val === n) return root;
return n < root.val ? search(root.left, n) : search(root.right, n);
}
这不就是小时候玩的“我想一个 1~100 的数,你来猜”吗?每次都能砍掉一半可能性!
- 目标值比当前小?去左边找!
- 目标值比当前大?去右边冲!
- 找到了?恭喜你,命中目标🎯!
2️⃣ 插入:找个“合法户口”安家落户
function insertIntoBst(root, n) {
if (!root) return new TreeNode(n);
if (n < root.val) {
root.left = insertIntoBst(root.left, n);
} else {
root.right = insertIntoBst(root.right, n);
}
return root;
}
插入新节点时,BST 会一路“导航”到合适的位置,确保不破坏原有的大小秩序。
就像小区物业:新住户不能随便住,必须按门牌号(数值大小)分配房子,左边小号,右边大号!
3️⃣ 删除:最难的“拆迁办主任”
删除是 BST 里最 tricky 的操作,因为要保证删完之后,树还是 BST!
分三种情况:
| 情况 | 处理方式 |
|---|---|
| 叶子节点(没孩子) | 直接删,不留痕迹 ✅ |
| 只有一个孩子 | 孩子“顶替”父位 👶→👨 |
| 有两个孩子 | 找“替身”! 要么用左子树最大值(前驱), 要么用右子树最小值(后继) |
function deleteNode(root, n) {
if (!root) return root;
if (root.val === n) {
// 没孩子 → 直接消失
if (!root.left && !root.right) return null;
// 有左子树 → 找左子树最大值(前驱)
if (root.left) {
const maxLeft = findMax(root.left);
root.val = maxLeft.val;
root.left = deleteNode(root.left, maxLeft.val);
}
// 否则用右子树最小值(后继)
else {
const minRight = findMin(root.right);
root.val = minRight.val;
root.right = deleteNode(root.right, minRight.val);
}
}
else if (n < root.val) {
root.left = deleteNode(root.left, n);
} else {
root.right = deleteNode(root.right, n);
}
return root;
}
💬 为什么不用直接删?
因为如果直接删掉一个有两个孩子的节点,树就“断层”了!必须找个“价值观相近”的节点来继承位置,而左子树最大值或右子树最小值正好夹在左右之间,是最合适的“和事佬”。
🧠 四、思考:BST 的“理想 vs 现实”
- 理想状态:完全平衡(如满二叉树),高度 ≈ log₂n,查找飞快 ✈️
- 现实打击:如果插入顺序是递增的(1,2,3,4,5…),BST 就退化成链表,O(n) 查找,慢如蜗牛 🐌
所以,实际工程中,我们往往会用 AVL 树、红黑树等自平衡 BST 来避免这种悲剧。
但!理解普通 BST,是通往这些高级结构的必经之路。
🎉 五、总结:BST 的三大美德
- 有序性:中序遍历 = 升序排列(天然排序神器!)
- 高效性:平均 O(log n) 的查找/插入/删除
- 简洁性:代码逻辑清晰,递归写起来行云流水
它或许不够“强壮”(怕退化),但它足够“优雅”。就像一位讲究秩序的绅士,虽然偶尔会被现实打脸,但初心不改。
📌 彩蛋:试试手写一遍!
// 中序遍历 → 输出升序!
function inorder(root) {
if (!root) return;
inorder(root.left);
console.log(root.val);
inorder(root.right);
}
对上面的例子调用 inorder(root),你会看到:1 3 4 6 7 8 9 —— 完美升序!
❤️ 写在最后
下次当你听到“二叉搜索树”,别再觉得它高冷难懂。
它只是一个执着于排序的小可爱,用最朴素的规则,撑起了高效查找的半边天。
**记住:数据结构不可怕,可怕的是你还没开始理解它。