Computing · Data Structures

Binary Search Tree (BST)

二叉搜索树 (BST)

~ 16 min · v24

二叉搜索树 BST

cs-ds/binary-search-tree · COMP2521
0 / 5
▶ Video walkthrough
Binary search trees in 33 seconds

Watch the short video to grasp it faster.

Watch on YouTube

1 一句话定义

二叉搜索树(BST)就是一棵对每个节点都满足"左子树都比它小、右子树都比它大"的二叉树。

像查纸质字典:翻到中间,要找的词比这页小就往前半本翻、大就往后半本翻,每翻一次就砍掉一半。BST 把"二分查找"长进了树的结构里——从根开始比大小,小往左、大往右,一路走到底。树越平衡,查找越快(O(log n));长歪成一条链就退化成 O(n)。
50 30 70 20 40 60 80 ← 都比 50 小 都比 50 大 →
对每一个节点都成立:左子树全部更小、右子树全部更大。中序遍历这棵树 → 20 30 40 50 60 70 80(自动升序!)。

2 模型 · 插入 / 查找 / 中序 / 删除

三种操作共用一条规律:从根开始比大小,小往左、大往右。点上面切换,点 ▶ 单步看左边树、右上代码当前行、右下"比较过程"如何联动。

BST 在代码里怎么写?——插入与查找

节点和普通二叉树一样;BST 的灵魂在 insert/search 里的那次比较:比当前小就递归进左子树,大就进右子树,走到空位就放/就判定没找到。

// 节点定义同二叉树
struct node { int value; struct node *left, *right; };

// 插入:从根往下找空位,比小走左、比大走右
node *insert(node *t, int v) {
    if (t == NULL) return newNode(v);              // 空位 → 放新节点
    if      (v < t->value) t->left  = insert(t->left,  v); // 小 → 左
    else if (v > t->value) t->right = insert(t->right, v); // 大 → 右
    return t;                                       // 相等:已存在,不重复插
}

// 查找:一样的走法,命中返回 true,走到空返回 false
bool search(node *t, int v) {
    if (t == NULL)        return false;        // 走到空 → 没找到
    if (v == t->value)    return true;         // 命中
    return v < t->value ? search(t->left, v) : search(t->right, v);
}

插入和查找走的是同一条路(都从根比大小往下)。所以两者代价都 = 走过的层数 = 树高 O(h)。h 在平衡时是 log n,退化成链时是 n。下面"插入建树"演示就是把 50 30 70 20 40 60 80 依次插进一棵空树。

BST 演示器

代码(当前行高亮)
比较过程
提示
点 ▶ 播放,看一个值如何从根开始比大小、一路找到自己的位置。

3 核心要点 · 4 句人话看懂 BST

BST = 二叉树 + 一条"左小右大"的硬规矩。

① BST 性质:左 < 根 < 右
一句话:对每个节点,它左边整棵子树都比它小、右边整棵子树都比它大——不是只比直接孩子,是整棵子树。
这条规矩让"从根比大小往下走"能精确定位任何值。违反它(比如把 80 放到 50 左边)就不再是 BST,查找会失灵。
50 / \ <50 >50 ← 整棵子树都满足
② 查找/插入:比大小往下走,O(h)
一句话:比当前小往左,大往右,走到空位就是"插入点 / 没找到"。代价 = 走过的层数。
查找和插入同一条路。代价正比于树高 h:平衡时 h≈log n(很快),退化成链时 h=n(和数组线性查找一样慢)。
v < t->value → 往左 v > t->value → 往右 t == NULL → 空位/没找到
③ 中序遍历 = 升序(招牌性质)
一句话:中序遍历任何 BST,结果一定从小到大排好序。反过来,这也是验证"是不是 BST"的办法。
因为中序是"左→根→右",而左全比根小、右全比根大。想得到有序数据?把它们插进 BST 再中序遍历即可。
中序(本页树) = 20 30 40 50 60 70 80 升序
④ 删除:两个孩子最麻烦
一句话:删叶子直接删;删只有一个孩子的,让孩子顶上;删有两个孩子的,要用中序后继(右子树最小)来替身。
删除有两个孩子的节点是最经典的坑:不能直接删(会断开子树),要找右子树里最小的那个(中序后继)顶替,再删掉那个后继。
删 50:找右子树最小 = 60 60 顶替 50,再删原来的 60

4 错误实验室 · 点一下亲眼看 bug

BST 最常翻车的几个点。点击卡片看症状。

① 比较方向写反
小往右、大往左 → 树建歪了,查找永远找不到
② 删两孩子节点直接删
没找后继顶替就删 → 整个子树丢失
③ 按有序插入 → 退化成链
1 2 3 4 5 顺序插 → 高度 = n,查找退化 O(n)
// 点击上方任一卡片,查看错误代码与运行效果
$ ./a.out (等待运行...)

5 即时小测 · 5 题 + 动手例题

用上面那棵 BST。先想再点。

去做例题:二叉搜索树 · 插入与查找
去做例题

读完这节,你应该明白:

BST = 二叉树 + 一条「左小右大」的硬规矩,而这条规矩的全部意义在于——查找和插入都能"从根比大小往下走",像翻字典一样每步砍掉一半(O(h)),中序遍历还自动得到升序。但关键的另一半是:BST 的快慢完全取决于树平不平衡——按有序数据插入会退化成一条链、掉回 O(n),和没用树一样慢。那怎么强制让树保持平衡?这正是下一站 AVL 要回答的问题 ↓

1 2 3 4 5 每个都比前一个大 → 一路往右挂
按 1 2 3 4 5 顺序插入空 BST:退化成这条右斜链,高度 = n、查找掉回 O(n)——这正是需要 AVL 自动旋转的原因。

⌨ 键盘快捷键

→
单步前进
←
单步后退
Space
播放 / 暂停
1/2/3
切插入/查找/中序
R
重置
?
打开本帮助