在大屏或电脑上体验更佳——动画和操作区域更宽敞。
输入

二叉搜索树

二叉搜索树在每个节点维护同一规则:左子树所有值更小,右子树所有值更大。比较一次便能排除一侧,插入、搜索和删除都沿高度 h 的路径运行,时间为 O(h)。树保持平衡,或满足合适的随机输入假设时,平均为 O(log n);普通 BST 不保证每次比较都排除一半。

猜数字时,“更大”或“更小”决定下一步。平衡的二分过程约二十次判断能覆盖一百万个候选。BST 把这种分支选择存成节点和指针,效果取决于树形。

二叉搜索树如何工作

三种操作都从根比较:小于当前值走左,大于走右,相等则命中。区别在路径结束后的动作。

insert(value) 走到空位置,再把新节点接为叶子,不调整上方结构。遇到相等值就停止,本实现不保存重复值。空树插入直接设置根。

search(value) 命中返回找到,走到 null 返回未找到。排序不变量保证被跳过的子树不可能包含目标。

delete(value) 找到目标后按子节点数处理:

  • 没有子节点:让父节点指向它的引用变为 null。
  • 一个子节点:让父节点跳过目标,直接接它的子节点;子树整体保留,仍满足相对父节点的顺序。
  • 两个子节点:本课使用中序后继。先到右子树,再一路向左,取其中最小值。它大于目标左子树所有值,也小于右子树剩余值。把后继值复制到目标,再从右子树删除后继。后继没有左孩子,所以删除归结为叶子或单子节点情况。中序前驱也可对称实现,本课代码采用后继。

逐步演示

播放器从空树开始。依次插入 50, 30, 70, 20, 40, 60, 80。

  1. insert(50):空根,无需比较,50 成为根。
  2. insert(30):30 < 50,向左接入。insert(70) 对称地接到右侧。
  3. insert(20):依次与 50、30 比较,向左两次。随后 40、60、80 各比较两次,分别接到 30 右侧、70 左侧、70 右侧。形成高度 2 的平衡树。
  4. search(40):在 50 处走左,在 30 处走右,在 40 处相等。三次比较,在深度 2 找到。
  5. delete(20):经 50、30 找到叶子 20,把 30 的左引用清空。
  6. delete(30):此时 30 只有子节点 40。把 50 的左引用改为 40,再移出 30,40 上移。
  7. delete(50):第一步命中根,根有两个子节点。向右到 70,再向左到 60。60 没有左孩子,是后继。复制 60 到根,再移除右子树中原来的叶子 60。

最终根为 60,左为 40,右为 70,70 的右侧为 80,每个节点仍满足排序规则。

画布的游标点亮比较路径,未访问子树保持淡色。插入前显示空位,删除分别展示剪除、旁路连接或后继搜索。后继与目标通过虚线关联,再执行复制和移除。

时间与空间复杂度

操作平均,需相应形状或输入假设最坏退化
搜索O(log n)O(n)
插入O(log n)O(n)
删除O(log n)O(n)
存储O(n)O(n)

普通 BST 不主动平衡。依次插入 1, 2, 3, 4, 5,每个新节点都接在前一个右侧,形成链。按边计高度为 n − 1,最坏操作 O(n)。AVL、红黑树和 treap 通过各自规则控制高度;例如 Java TreeMap 使用红黑树。

最大深度滑块 3–5 是画布布局限制。插入超出显示深度时,播放器说明原因;达到该深度可容纳的完美树节点上限后,插入按钮禁用。这不是 BST 的结构限制,实际树可以继续退化增长。

边界情况

  • 空树:搜索立即未找到;插入创建根;删除不改变状态。
  • 删除根:双子节点用后继;单子节点让孩子成为根。
  • 重复插入:遇到相等值即停止。也可另设计数字段等策略,但同一棵树内必须一致。
  • 删除缺失值:走到 null,树不变。
  • 删除唯一节点:叶子情况,根变空。

常见错误

  • 直接移除双子节点目标而不保留连接。 会丢失子树。本课先复制后继值,再移除后继;也可采用维护相同不变量的节点移植算法。
  • 找错后继。 后继是右子树最左节点,不是任意附近叶子,也不是左子树节点。若使用前驱,要采用对称的完整算法。
  • 有序输入却期待平衡。 普通插入会形成链。需要最坏界时使用自平衡树,随机顺序也不是最坏情况保证。
  • 混淆 BST 与堆。 BST 的左右顺序支持有序遍历和范围查询;堆约束父子优先级,能 O(1) 查看极值,但数组并不有序。