二叉搜索树与平衡树
本节目标
- 掌握 BST 的查找 / 插入 / 删除
- 理解"中序遍历验证 BST"的原理
- 知道 AVL / 红黑树为何存在(平衡的意义)
BST 的性质:左子树所有值 < 根 < 右子树所有值。据此查找/插入只需沿一条路径走,平均 O(log n)。
// 运行环境:Node.js 14+
// 保存为 dsa-l12.js,执行:node dsa-l12.js
class TreeNode {
constructor(val) { this.val = val; this.left = null; this.right = null }
}
function insert(root, val) {
if (!root) return new TreeNode(val)
if (val < root.val) root.left = insert(root.left, val)
else if (val > root.val) root.right = insert(root.right, val)
return root
}
function search(root, val) {
while (root) {
if (val === root.val) return root
root = val < root.val ? root.left : root.right
}
return null
}
// 删除:①叶子直接删 ②单子节点接上 ③双子节点→用"后继"(右子树最小)替身
function remove(root, val) {
if (!root) return null
if (val < root.val) root.left = remove(root.left, val)
else if (val > root.val) root.right = remove(root.right, val)
else {
if (!root.left) return root.right
if (!root.right) return root.left
let succ = root.right
while (succ.left) succ = succ.left
root.val = succ.val
root.right = remove(root.right, succ.val)
}
return root
}
// 中序验证 BST(升序即通过)
function isValidBST(root, prev = { v: -Infinity }) {
if (!root) return true
if (!isValidBST(root.left, prev)) return false
if (root.val <= prev.v) return false
prev.v = root.val
return isValidBST(root.right, prev)
}
let r = null
;[5, 3, 7, 2, 4].forEach(v => r = insert(r, v))
console.log(search(r, 4)?.val, isValidBST(r)) // 4 true平衡树的意义:若插入有序数据,BST 会退化成链表(O(n))。AVL / 红黑树通过旋转保持左右子树高度差有界,维持 O(log n)。JS 的 Map 在部分引擎底层即红黑树。
名词解释
- 二叉搜索树(BST):左 < 根 < 右 的二叉树,查找/插入/删除平均 O(log n)。类比:二分查找的"树形版"。
- 后继(Successor):中序遍历中某节点的下一个节点。删除"双子节点"时,用右子树的最小节点(最左)替身,能保持 BST 性质。
- 平衡树(Balanced Tree,AVL/红黑树):通过旋转限制树高,避免退化成链表,保证操作稳定 O(log n)。
课后练习
- BST 删除"双子节点"为什么用右子树最小值替身?
- 答案:右子树最小值(最左节点)比左子树都大、比右子树其余都小,恰好满足 BST 的"左<根<右",替身后整棵树性质不变。
- 为什么有序插入会让 BST 退化成链表?
- 答案:每次新值都比当前最大值大,永远只往右走,树变成一条向右的链,查找退化 O(n)。需要平衡树来救。
总结
BST 是把"二分查找"从数组搬到了树上,让"动态插入/删除"也能保持 O(log n) 的查找效率——这是它比静态排序数组强的地方。但 BST 有个致命弱点:数据有序时它会歪成链表,性能雪崩。这一课真正重要的不是 insert/remove 那几行代码,而是引出"平衡"这个贯穿数据结构的核心命题:任何依赖"树高"的复杂度,都必须防止树被拉偏。AVL、红黑树正是为解决这个问题而生,它们用旋转把树高锁在 O(log n)。理解到这一层,你再看 Map、数据库索引(B+ 树)、甚至搜索引擎的倒排结构,都会会心一笑:原来都是"在动态与有序之间找平衡"。