想象你走进一家藏书百万的「悦读图书馆」。没有索引时,找一本书要一本本翻;如果管理员把所有书按「比这本大挂右边,比这本小挂左边」的规则,摆成一棵有层次的「书架树」,那么每走一层,你只需判断一句“目标在左还是右”——几千万本书,二三十次判断就能找到。这就是搜索树。但书架摆法大有讲究:新书上架顺序可能把整齐的树拉成歪斜长廊;一丝不苟的小A每次上架都严格整理(AVL树);灵活的小红用红黑标签快速整理(红黑树);书库大到放上磁盘后,还得用「一箱一箱搬」的B树。平衡、维护成本、磁盘IO……这些权衡决定了查找效率的最终答案。

岗位关联

对 AI 从业者来说,搜索树是数据库索引、内存缓存、文件系统与特征存储的地基——看懂它的查找效率与选型逻辑,才能定位数据管道的性能瓶颈,设计出能扛住海量样本的训练与推理系统。

目录
  1. 可视化演示
  2. 二叉搜索树:馆长最朴素的排架想法
  3. AVL平衡树:处女座管理员小A的严格整架
  4. 红黑树:用红黑标签管理繁忙书库
  5. B树家族:磁盘世界的分馆体系
  6. 效率对比:书架方案的成本账本
  7. 树高本质:楼梯层数与信息论
  8. 应用场景:不同书库的最终选择
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

对比BST退化为链表的最坏情况与AVL平衡树的查找路径,展示各搜索树的时间复杂度差异,理解平衡策略对查找效率的影响。

本节配有交互动效(进入视口后自动加载,约 3KB)。

BST vs AVL:平衡的力量 BST:顺序插入退化为链表,高度O(n) AVL:旋转保持平衡,高度O(log n) height(balanced) = O(log n) 平衡策略将最坏查找从O(n)优化到O(log n)。

二叉搜索树:馆长最朴素的排架想法

悦读图书馆开业那天,馆长老陈定了一条朴素的规矩:从「根书架」开始,新到的书比当前书架小就挂到左边,大就挂到右边。这个规则的名字叫二叉搜索树(BST)——每个节点最多两个孩子,且永远满足“左子树所有键 < 根键 < 右子树所有键”。找书时,你从根书架出发,目标比当前书架小就拐向左廊,大就拐向右廊,每次决策都排除掉一半书架。

这个「向左还是向右」的抉择,正是二分查找思想的树形表达。插入也能复用查找路径:一路找到空位、把新书架挂上去即可。删除分三种情况:叶子直接摘掉;只有一个孩子的节点让孙子顶替;有两个孩子的节点则找右子树的最小键来「替身继位」。三个操作都只需走一条从根到叶子的路,代价就是树的高度。

但朴素的规矩有致命弱点:如果进货单恰好按从小到大排列,新书永远挂在最右边,整棵树会被拉成一条歪斜长廊。此时看似有个漂亮的树形,实际退化成链表。

老陈很快意识到,随机摆架时平均很快,但最坏情况的「歪斜长廊」风险实在太大。于是他决定请一位一丝不苟的管理员来维持书架的整齐——这就是 AVL 树的登场。

T(n)={O(log⁡n),平均情形(随机插入)O(n),最坏情形(退化为链表)T(n) = \begin{cases} O(\log n), & \text{平均情形(随机插入)} \\ O(n), & \text{最坏情形(退化为链表)} \end{cases}
这个公式说:在随机顺序下,查找百万量级的藏书大约只需 20 步;但如果运气极差、上架顺序有序,则可能要找 100 万步——差距悬殊。
  1. T(n) 表示查找所需的比较次数它是衡量查找效率的核心指标,一次比较相当于在图书馆走一层楼梯。
    T(n)T(n)
  2. O(log n) 平均情形随机插入时树高约为对数级别,每走一层排除一半书架,这是二分思想带来的巨大收益。
    O(log⁡n)O(\log n)
  3. O(n) 最坏情形有序插入导致每个新节点都挂在同一侧,树高退化为 n,查找演变为从头走到尾的线性扫描。
    O(n)O(n)
  4. 退化为链表树形结构被拉直,左右决策退化成“下一个”的机械移动,二分优势荡然无存。
    BST→有序插入链表\text{BST} \xrightarrow{\text{有序插入}} \text{链表}

AVL平衡树:处女座管理员小A的严格整架

馆长请来了「处女座」管理员小A,他的座右铭是:任何书架左右两侧的高度差不能超过 1。每上架一本书,小A就从新书架一路往回检查,发现某个架的左边比右边高出两层以上,立刻推着书架做「旋转」——把多出来的那一侧翻上去,让矮的一侧获得平衡。

这个「左右高度差」的专业名字叫平衡因子(Balance Factor),定义为左子树高度减右子树高度,只允许取 -1、0、1。当某个节点的平衡因子越界为 ±2 时,根据失衡方向执行四种旋转:LL、RR、LR、RL。前两者是单旋,后两者是双旋(先局部单旋、再整体单旋)。旋转的本质,是把「被拉长的走廊」重新折叠成饱满的金字塔。

由于每个节点都被严格量过身高,AVL 树的树高被压到极低。对任意 n 个节点,AVL 树的最大高度有紧致的上界——大约 1.44 倍的理想对数高度。

小A把图书馆打理得无可挑剔,但每次上架新书都要来回检查、频繁转书架。馆长发现:如果图书馆以「借阅」为主、很少进新书,这套方案近乎完美;可如果每天都有上千本新书涌入,整理成本就成了瓶颈。

hmax⁡(n)≤1.44log⁡2(n+2)−1.328h_{\max}(n) \le 1.44\log_2(n+2) - 1.328
这个公式说明:即使面对 100 万个节点,AVL 树也能保证查找最多约 28 步,几乎是完美二分的高度。代价自然是每次插入、删除都要做重新「量身高」和旋转。
  1. h_max(n) 树的最大可能高度在给定节点数 n 时,AVL 树能达到的最坏高度,决定了一次查找的最多比较次数。
    hmax⁡(n)h_{\max}(n)
  2. 1.44 对数前的系数由于平衡条件放宽到「差不超 1」,树高比完全平衡的 \log_2 n 略高,但系数仍接近 1。
    1.44log⁡2(n+2)1.44\log_2(n+2)
  3. 减 1.328 常数修正这是一个精确的收敛常数,让上界更贴合小规模场景,工程上也可直接当作「树高 ≈ 1.44 倍对数」使用。
    −1.328- 1.328

红黑树:用红黑标签管理繁忙书库

老陈又请来灵活的管理员小红。小红不精确测量书架高度,而是给每个书架贴一张红色或黑色的标签,只守几条简单规则:根书架必须是黑色;红色标签的书架不能直接接红色标签的孩子;从根到任何一个空叶子,路径上经过的黑色标签数量必须完全相同。这五条性质就是红黑树的定义。

这些规则看似随意,却精确制造出一个关键约束:最长路径不会超过最短路径的两倍。因为红色不能连续,最长路径最多是「黑-红-黑-红」交替;又因为每条路径黑节点数相同,最短路径全是黑色——于是红色越多,路径也就越长,但最长恰好是最短的两倍。这意味着红黑树不需要像 AVL 那样精确旋转,只需通过「变色」和少量旋转就能维持大致平衡。

插入时先按 BST 规则把节点挂成红色,再沿路径向上修复:叔节点是红色就整体变色,叔节点是黑色就旋转。由于性质宽松,红黑树的重平衡操作次数远少于 AVL。树高上界也随之放宽:

小红的管理方式让书库在「借还频繁」的场景下运转如飞。Java 的 TreeMap、C++ 的 std::map、Linux 内核的 CFS 调度器都用它。不过,当书库大到需要搬到磁盘上时,新的物理限制出现了——一次取书的成本从「走一层楼梯」变成了「从巨大的中央仓库搬一箱书」。

h≤2log⁡2(n+1)h \le 2\log_2(n+1)
这个公式表明红黑树虽然比 AVL 高约 40%,但依然是严格的对数级别。它用「略高的树」换来了「显著更少的调整」,特别适合读多写也多的高频场景。
  1. h 树的高度同样代表最坏情况下从根走到叶子的比较次数,是查找延迟的上限。
    hh
  2. 2log₂(n+1) 两倍对数上界这个系数 2 直接来自「最长路径≤最短路径×2」的性质,比 AVL 的 1.44 宽松,但仍是 O(log n)。
    2log⁡2(n+1)2\log_2(n+1)
  3. 路径限制的意义红色不连续约束让树不会被拉成长链,即使最坏情况也保证对数级别的查找代价。
    hlongesthshortest≤2\frac{h_{\text{longest}}}{h_{\text{shortest}}} \le 2

B树家族:磁盘世界的分馆体系

国家图书馆的分馆体系给出了答案。每个分馆不再只放一本书,而是一整箱书——一个节点里塞进几百个键值,配几百个指向分馆的指针。这一「箱」的大小,恰好是磁盘一次读取的页大小(如 16KB)。查找时,每访问一个节点就是一次磁盘 IO,但因为箱子里键多,一趟就能排除掉几百个方向,树高迅速塌缩。

这种多路搜索树就是 B 树(B-tree)。一个 m 阶 B 树,每个节点最多 m-1 个键、m 个孩子;所有叶子在同一层,保证任何查找的磁盘 IO 次数都是树高 h。而树高与阶数 m 的关系是——底数越大,指数越小:

B+ 树是 B 树的完美升级:只有叶子节点存数据,内部节点只存键用于路由;叶子之间用链表串成有序序列。这样内部节点能塞更多键、树更矮,范围查询还能从链表上直接顺序扫描,无需回溯。MySQL InnoDB 的聚簇索引,正是 B+ 树的典型应用。

NIO=⌊log⁡mn⌋+1N_{\text{IO}} = \left\lfloor \log_m n \right\rfloor + 1
这个公式揭示了 B 树的核心秘密:一次磁盘访问能「搬回」m 个方向的决策,于是千万级数据只需 3 到 4 次 IO。对比二叉树的 20 多次,降幅惊人。
  1. N_IO 磁盘访问次数一次查找需要从磁盘读取的节点数量,等于树高再加 1(每层读一次)。
    NION_{\text{IO}}
  2. log_m n 以扇出 m 为底的对数m 是一个节点能容纳的键数,通常取几百。底数越大,树高越低,IO 次数越少。
    log⁡mn\log_m n
  3. 加 1 表示根节点根通常常驻内存缓存,实际 IO 次数往往比公式少一次。
    +1+1

效率对比:书架方案的成本账本

老陈面前摆着四份书架方案报价单,像极了一笔预算决策。BST 零维护但可能歪斜;AVL 最工整但每次上架都要转书架;红黑树折中;B 树要为「一箱多书」付出节内二分查找的额外计算。真实的查找开销,不能只看树高,还要看每一层付出什么代价。

如果把一次查找的总开销拆开,会发现它由两部分组成:每层的计算/旋转开销 × 层数,加上每次磁盘 IO 的巨额开销 × IO 次数。内存比较快到纳秒级,磁盘 IO 却要毫秒级——差了六个数量级。这就是为什么在磁盘场景下,B 树「每层更贵但层数极少」的策略完胜。

从复杂度渐进意义上看,AVL、红黑树、B 树都是 O(log n),常数因子才是真正的分水岭。那么,树高为什么对查找效率有如此决定性的影响?答案藏在「每一层楼梯排除一半可能性」这个信息论直觉里。

Cost=c×h+d×m\text{Cost} = c \times h + d \times m
这个公式说明:BST 的 c 很小但 h 可能很大;B 树的 c 略大但 h 极小;而一旦 d 出现(磁盘 IO),任何 h 的劣化都会被放大百万倍。选型本质是权衡「每步代价」和「步数」。
  1. Cost 单次查找总开销综合了 CPU 比较、指针跳转和磁盘 IO 的端到端延迟,是选型的最终判据。
    Cost\text{Cost}
  2. c × h 层次计算开销c 是走一层的平均成本(比较加分支跳转),h 是树高。两者相乘得到「纯内存侧」的代价。
    c×hc \times h
  3. d × m 磁盘访问开销d 是一次磁盘 IO 的时间(约 5-15ms),m 是访问的磁盘节点数。d 通常比 c 大百万倍,所以 m 只要多 1,总开销便大幅飙升。
    d×md \times m

树高本质:楼梯层数与信息论

想象一栋完美的「满二叉树」图书馆:第 0 层有 1 个书架,第 1 层有 2 个,第 2 层有 4 个……每往下一层,书架数量翻倍。如果 n 个书架填满这栋楼,楼层数 h 和书架总数的关系就是等比数列求和:

平衡树的全部意义,就在于让这栋「书架楼」尽可能接近满二叉树——不让任何一条走廊特别长。理解了树高与对数关系,再回头看成百上千种树结构,本质都是在回答同一个问题:如何让每层排除的比例尽可能高、同时让维护成本尽可能低。

n=2h−1⟹h=log⁡2(n+1)−1n = 2^{h} - 1 \quad \Longrightarrow \quad h = \log_2(n+1) - 1
这个公式是整篇文章的灵魂:10 亿条数据时,\log_2(10^9+1) 约等于 30——只需 30 次比较就能定位任意一条。树高的本质,就是二分次数,是「每次淘汰一半」策略的必然结果。
  1. n = 2^h − 1 满二叉树节点总数第 i 层有 2^i 个节点,从 0 加到 h-1 层,等比数列求和得到总节点数。
    n=∑i=0h−12i=2h−1n = \sum_{i=0}^{h-1} 2^i = 2^h - 1
  2. h = log₂(n+1) − 1 反解树高把 n 当成自变量,解出楼层数的表达式,展示出树高与节点数的对数关系。
    h=log⁡2(n+1)−1h = \log_2(n+1) - 1
  3. 每次淘汰一半的意义每比较一次,搜索空间从 n 缩减到 n/2,重复 h 次后空间只剩 1,这就是二分搜索能实现指数级压缩的原因。
    n→n2→n4→⋯→1n \to \frac{n}{2} \to \frac{n}{4} \to \cdots \to 1

应用场景:不同书库的最终选择

老陈终于拍板了方案:总馆藏书室用红黑树(内存热数据),中央大库用 B+ 树(磁盘海量数据),分馆索引柜用 B 树变体(文件系统目录)。三套方案对应三种物理介质与工作负载的匹配。

数据库索引是 B+ 树最辉煌的战场。MySQL InnoDB 的聚簇索引把主键和数据行存在叶子节点,二级索引的叶子存主键值;因为叶子链表有序,范围查询和排序也能高效完成。内存索引则偏爱红黑树和 AVL:Redis 的过期字典、Java 的 TreeMap、Linux 内核的红黑树,都在纳秒级延迟下工作。文件系统如 ext4 使用 Htree(哈希化的 B 树)加速目录查找。选型依据可以归纳为三个问题:数据量多大?读写比多少?要单点查还是范围扫?

范围查询的开销公式,解释了 B+ 树为何不可替代:

至此,馆长的四份书架方案各归其位。搜索树的终极智慧在于:没有最好的树,只有最匹配场景的树——理解了二叉搜索树的基础、AVL 的严格、红黑树的灵活、B 树的磁盘哲学,你便读懂了索引与存储系统的设计密码。

Trange=O(log⁡mn+k)T_{\text{range}} = O(\log_m n + k)
这个公式说明:范围查询先花 \log_m n 定位起点,再沿叶子链表顺序扫描 k 个结果。对亿级表做区间聚合,B+ 树能稳定保持在几十毫秒内,而二叉树每回溯一层都可能再触发一次磁盘 IO。
  1. T_range 范围查询总代价返回一批连续键值所花费的时间,是分析「where 条件扫描」的工具。
    TrangeT_{\text{range}}
  2. log_m n 定位起点的代价先用 B+ 树从根走到第一个目标叶子,走的是树高那么长的路,IO 次数极少。
    log⁡mn\log_m n
  3. k 扫描结果数从起点沿着叶子链表逐个读取 k 条记录,代价与返回行数成正比,与总数据量无关。
    +k+k

AI 应用场景

训练样本库的版本索引

阶段:数据管道与特征存储

解决的问题:训练集通常按时间戳分版本存储,传统方案扫描元数据表挑选某时间段的样本,数据量过亿时耗时可到分钟级。用 B+ 树按时间戳建索引后,范围查询通过叶子链表顺序扫描,毫秒级返回版本清单,大幅缩短数据准备时间。

具体案例:某推荐系统团队维护每日更新的特征存储,用 MySQL B+ 树索引记录「样本ID ↔ 特征版本」映射。原先全表扫描耗时 3 分钟,改造为时间戳 B+ 树索引后降至 40ms,训练数据的加载速度提升近 4000 倍。

在线推理的 Top-K 内存索引

阶段:模型服务与近线检索

解决的问题:实时推荐需要对候选池做「滑动窗口内取 Top-K」操作,普通数组在频繁插入删除时性能差。红黑树保持有序性且插入删除 O(log n),还能以 O(log n) 找到窗口边界再做有序遍历,支撑毫秒级候选过滤。

具体案例:一个广告 CTR 预估服务每秒处理 5 万次请求,用 Java TreeMap(红黑树实现)按预估得分维护候选广告有序集合,每个请求在 3ms 内完成 Top-100 截断,替代了原先 20ms 的排序方案。

海量模型文件系统的目录索引

阶段:大规模训练集管理与 MLOps

解决的问题:LLM 训练集包含数十万个子目录,文件系统逐个目录线性查找会拖慢数据加载。ext4 的 Htree(B 树变体)利用目录项的哈希值建多路索引,把目录查找从线性变为 O(log_m n),极大加速海量分片数据的定位。

具体案例:某大模型团队把 SFT 数据按「任务类型/质量分桶」切分成 20 万个子目录存放。使用支持 Htree 的 ext4 文件系统后,随机访问一个样本文件的路径解析从 50ms 降到 2ms,数据加载瓶颈得到缓解。

Python 演示

python64 行
# 二叉搜索树查找效率对比:随机插入 vs 有序插入import random class TreeNode:    def __init__(self, key):        self.key = key        self.left = None        self.right = None class BST:    def __init__(self):        self.root = None     def insert(self, key):        if self.root is None:            self.root = TreeNode(key)            return        node = self.root        while True:            if key < node.key:                if node.left is None:                    node.left = TreeNode(key)                    return                node = node.left            else:                if node.right is None:                    node.right = TreeNode(key)                    return                node = node.right     def height(self, node=None):        if node is None:            node = self.root        if node is None:            return 0        return 1 + max(self.height(node.left), self.height(node.right))     def search_path_len(self, key):        node = self.root        steps = 0        while node is not None:            steps += 1            if key == node.key:                return steps            node = node.left if key < node.key else node.right        return steps def build_report(values, name):    tree = BST()    for v in values:        tree.insert(v)    probe = values[len(values) // 2]    s = tree.search_path_len(probe)    print(name + ": 节点数=" + str(len(values)) +          ", 树高=" + str(tree.height()) +          ", 找中间键比较=" + str(s) + "次") random.seed(42)n = 1024data_random = list(range(n))random.shuffle(data_random)data_sorted = list(range(n))build_report(data_random, "随机插入")build_report(data_sorted, "有序插入")
目的:构造同样的 1024 个键,一份随机顺序插入、一份升序插入,对比两棵 BST 的树高与查找比较次数,直观验证「插入顺序决定查找效率」。
  1. self.root = TreeNode(key)空树时第一个键成为根书架,这是整棵树的起点
  2. while True:用迭代代替递归做插入,避免深树递归导致栈溢出
  3. if key < node.key:核心比较:决定新书架挂在左廊还是右廊
  4. node.left = TreeNode(key)找到空位后挂上新节点,完成一次插入
  5. return 1 + max(...)后序遍历递归计算树高,树的层级数即最坏查找步数
  6. while node is not None:沿查找路径逐层下行,统计比较次数
  7. random.shuffle(data_random)打乱数据模拟「随机上架」的正常情况
  8. data_sorted = list(range(n))升序序列模拟「最坏进货单」,必然退化成链表
  9. build_report(data_random, ...)用同一函数输出两组指标,形成鲜明对照

常见误区

误区AVL 树更矮更平衡,查找一定更快,那数据库和系统库为什么不用 AVL?肯定是大家没发现它的好,我项目里全部用 AVL 就对了。

正解AVL 的查找确实快一点——但两个都是 O(log n),差距只是 1.44 和 2 的常数因子。为了换这点提升,AVL 每次插入删除都要做更频繁的旋转,写操作多的场景反而更慢。红黑树用「略高的树」换「显著的写入收益」,才是工程界的共识之选。就像图书馆里,为每次进书都精确重排书架的书库,在借阅频繁时必然手忙脚乱。

为何常见:人们容易只看「树越矮越快」这一条直观结论,忽略了维护成本这个更常被触发的开销。

误区B 树不就是「超级二叉树」吗?一个节点存多个键、连多个孩子,二分查找换成多分查找,仅此而已。

正解多路分支只是表象,B 树的灵魂是「以磁盘页为单位的 IO 优化」。节点大小被刻意设计成恰好等于一次磁盘读写的页大小,内部键做二分查找定位孩子。相比二叉树的 20 次 IO,B 树把 IO 压到 3-4 次——省的不是比较次数,而是每次「搬一箱书」的巨额时间。脱离磁盘背景谈 B 树,就像只看比分不看比赛规则。

为何常见:初学者常从「树」的共性出发做类比,忽略了 B 树诞生背后的物理存储约束。

误区既然平衡树号称「高效查找」,那肯定是常数时间,跟哈希表差不多,直接 O(1) 嘛。

正解平衡树的一切操作都是 O(log n)。log2 100 万约等于 20——20 次比较确实极快,但绝不是 O(1)。与哈希表相比,平衡树胜在「有序性」:范围查询、顺序遍历、最值查找哈希表都做不到。若只需要等值查询且不在意顺序,哈希表才是更优选择。树不是魔法,它只是把「二分思想」稳定地兑现成对数步数。

为何常见:log n 增长太慢,人脑容易把它误当成常数,尤其在现代 CPU 上百纳秒比较下,20 次和 10 次几乎感觉不出差异。

误区书上说二叉搜索树查找是 O(log n),那我写代码时随便按什么顺序插入数据都没关系,反正查得快。

正解O(log n) 是「平均情形」,前提是插入序列近似随机。一旦数据有序或接近有序(如按 ID 递增写入),BST 退化成链表,查找变成 O(n)。生产代码里若不加平衡机制,一条 SQL 的等值查询可能在数亿行上演变为全表扫描。这正是 AVL、红黑树等自平衡树存在的理由——自动修复「坏进货单」带来的退化。

为何常见:教材默认给出平均复杂度,而工程数据的写入顺序往往带有强规律性,初学者容易忽略「输入分布决定复杂度」这一点。

面试题

Q1. 在 n 个节点的二叉搜索树中,随机均匀插入数据后,一次成功查找的平均时间复杂度约为多少?

单选题 · easy

  • A.O(1)
  • B.O(log n)← 正确答案
  • C.O(n)
  • D.O(n log n)

答案:B。随机插入形成的 BST 树高约为 O(log n),查找路径从根走到叶子,比较次数等于树高。B 正确。O(1) 是哈希表的特性,树做不到;O(n) 只在最坏退化时出现;O(n log n) 通常与排序算法相关,与单次查找无关。

Q2. 二叉搜索树在什么情况下会退化成近似链表?

单选题 · easy

  • A.数据量超过 100 万
  • B.插入序列基本有序(递增或递减)← 正确答案
  • C.键值类型是字符串
  • D.存在并发写入

答案:B。当插入序列单调递增时,每个新键都挂在最右侧,树退化成右斜链表,查找复杂度恶化为 O(n)。数据量大本身无害,随机插入仍是对数层;字符串键也可以用字典序比较;并发写入可能导致不一致,但不是退化的直接原因。

Q3. AVL 树中,任意节点的平衡因子(左子树高度减右子树高度)的取值范围是?

单选题 · easy

  • A.{0, 1}
  • B.{-1, 0, 1}← 正确答案
  • C.{-2, -1, 0, 1, 2}
  • D.任意整数

答案:B。AVL 的定义要求每个节点左右子树高度差绝对值不超过 1,所以平衡因子只能是 -1、0、1 三者之一。取到 ±2 表示失衡,需要触发旋转恢复。C 是旋转前可能出现的临时状态,不是合法稳态;A 漏掉了左高的情况;D 完全违背定义。

Q4. 红黑树中,从根到叶子的最长路径与最短路径的长度之比上界是多少?

单选题 · mid

  • A.1
  • B.2← 正确答案
  • C.log n
  • D.n

答案:B。红色节点不能连续,且每条路径黑节点数相同,因此最长路径是「黑-红」交替,长度最多为最短路径(全黑)的两倍。正是这个「2 倍」的宽松约束,让红黑树在保持 O(log n) 的前提下大幅减少旋转次数。A 是完全平衡树的比例;C、D 都不是路径长度比例的界。

Q5. 下列哪些数据结构属于自平衡搜索树?

多选题 · mid

  • A.AVL 树← 正确答案
  • B.红黑树← 正确答案
  • C.普通二叉搜索树
  • D.B 树← 正确答案

答案:ABD。AVL 树和红黑树在插入删除后会自动重平衡,保证 O(log n) 高度。B 树的叶子同层、节点分裂合并也属于自平衡多路搜索树。普通 BST 没有任何平衡机制,插入顺序恶劣时退化为链表,不属于自平衡结构。

Q6. 为什么 B 树 / B+ 树比二叉平衡树更适合做数据库磁盘索引?

单选题 · mid

  • A.实现起来更简单
  • B.每个节点容纳大量键,显著降低树高,减少磁盘 IO 次数← 正确答案
  • C.完全不需要内存
  • D.支持哈希冲突处理

答案:B。数据库的瓶颈是磁盘 IO,每次读节点都对应一次毫秒级访问。B 树每个节点可存上百个键,树高只有 3-4 层,一次查询只需几次 IO;二叉树即使平衡也要 20 次以上 IO。实现复杂度反而更高,也不存在「不需要内存」的结构,哈希冲突则与 B 树无关。

Q7. 一棵包含 100 万个节点的 AVL 树,一次查找最多大约需要比较多少次?(已知 log₂ 10⁶ ≈ 20)

单选题 · hard

  • A.20
  • B.29← 正确答案
  • C.40
  • D.100

答案:B。AVL 树最大高度约为 1.44·log₂(n+2) − 1.328,代入 n=10⁶ 得 1.44×20 − 1.3 ≈ 27.5,向上取整约 28 步,最接近 29。20 是完美平衡树的最小高度;40 接近红黑树的上界 2log₂;100 与对数界完全不符。

Q8. 关于红黑树的五条性质,下列哪些描述是正确的?

多选题 · mid

  • A.根节点必须是黑色← 正确答案
  • B.红色节点的两个子节点必须是黑色(红色不能连续)← 正确答案
  • C.任一节点到其所有后代空叶子的路径,包含相同数量的黑色节点← 正确答案
  • D.所有叶子节点必须标记为红色

答案:ABC。A、B、C 正是红黑树五条性质中的三条核心规则。D 错误:空叶子节点(NIL)统一视为黑色,不是红色。这五条合起来保证了最长路径不超过最短路径的两倍,从而锁定 O(log n) 树高。

Q9. B+ 树相比 B 树在范围查询上更优的关键原因是什么?

单选题 · hard

  • A.B+ 树的高度更低
  • B.内节点不存数据,叶子节点用链表相连,可顺序扫描← 正确答案
  • C.B+ 树支持更多并发锁
  • D.B+ 树的键允许重复

答案:B。B+ 树把数据全部集中在叶子节点,并用链表串成有序序列,范围查询找到起点后沿链表顺序扫描即可,无需像 B 树那样在内部节点间来回回溯。内部节点不存数据也让单节点容量变大、树变矮,但「链表顺序访问」才是范围查询优势的核心。并发锁和键重复均非关键区别。

Q10. 高度为 5 的 AVL 树至少需要多少个节点?已知最小节点数满足递推 N(h) = N(h-1) + N(h-2) + 1,且 N(0) = 1,N(1) = 2。

单选题 · extreme

  • A.12
  • B.15
  • C.20← 正确答案
  • D.32

答案:C。递推计算:N(2) = 2+1+1 = 4,N(3) = 4+2+1 = 7,N(4) = 7+4+1 = 12,N(5) = 12+7+1 = 20。高度为 h 的 AVL 树要构造「最瘦」形态,根的一棵子树取 h-1 层、另一棵取 h-2 层,因此最少 20 个节点。12 是高度 4 的答案;32 接近满二叉树 2⁶−1,比最少情况多得多。