考研学习
知识点讲义数据结构

第 7 章 查找

408 数据结构第 7 章 查找复习讲义,涵盖核心概念、重点考点与典型题型。

Last updated on

配套选择题也高度集中在这些过程题上:折半查找路径与 ASL、BST 查找序列是否合法、AVL 旋转、红黑树性质、B 树高度与插删、哈希探测序列等。

一、先建立整章框架

第 7 章本质上只研究一个问题:

给我一个 key,怎样尽快找到它?

不同查找方法,本质区别是:

查找结构代表方法核心思想典型复杂度408重要度
无特殊结构顺序查找一个一个比★★
有序顺序表折半查找每次排除一半★★★★★
分块结构分块查找先找块,再找元素约 ★★★
二叉搜索树BST小的向左,大的向右平均 ★★★★★
高度平衡BSTAVLBST + 强制平衡★★★★★
近似平衡BST红黑树放松平衡条件★★★★
多路平衡树B树一个结点放很多关键字 层★★★★★
数据库索引树B+树数据全放叶结点★★★
散列表Hash直接算地址平均 ★★★★★

所以这一章其实可以理解成:

顺序扫描 → 利用有序性 → 利用树结构 → 降低树高 → 最后直接算地址。

二、7.1 查找基本概念:重点是 ASL

1. 查找、查找表、关键字

查找就是在数据集合中寻找满足条件的数据元素。

结果只有两类:

  • 查找成功;
  • 查找失败。

用于查找的数据集合叫做查找表。

关键字 key 是能够标识记录的数据项。若能唯一确定记录,就是唯一关键字。

2. 静态查找表和动态查找表

这是后面各种结构的选择依据。

静态查找表主要进行查询,不经常插入、删除。

典型选择:

有序顺序表 + 折半查找。

动态查找表需要频繁地插入、删除、查询。

典型选择:

BST、AVL、红黑树、B树、散列表。

这里提醒一个资料内部的小口径问题:7.1 前部文字把“折半查找”也列入了动态查找相关方法,但同一资料后面的 BST 小节明确指出,对于需要频繁插删的动态表,应优先用二叉排序树,而折半查找对应的有序顺序表插删需要移动大量元素。这也是本章后续分析应采用的理解。

三、全章灵魂:平均查找长度 ASL

408这章大量计算题,最后都归结到它。

平均查找长度:

其中:

  • :查到第 个元素的概率;
  • :为了找到它进行关键字比较的次数。

等概率情况下:

于是:

一定记住:

ASL 算的不是元素下标,而是关键字比较次数。

四、7.2 顺序查找

1. 普通顺序查找

假设:

12  37  5  84  26  51

查找 26。

就是:

12 ≠ 26
37 ≠ 26
5  ≠ 26
84 ≠ 26
26 = 26

比较 5 次。

没有利用任何结构信息,因此最坏情况要把整个表检查一遍。

时间复杂度:

2. 哨兵技巧

资料中的实现从表尾向前搜索,并把第 0 号位置设成 key:

ST.elem[0] = key;

for (int i = ST.TableLen;
     ST.elem[i] != key;
     --i);

return i;

为什么放一个 key?

因为原来每一次循环都需要判断:

i 是否越界?
elem[i] 是否等于 key?

设置哨兵后,即使不存在目标,最后一定会在 0 号位置停止。

因此只需判断:

elem[i] != key

这就是“哨兵”的意义:

人为制造一个必定成功的位置,消除循环中的边界判断。

五、顺序查找的 ASL

资料算法从后向前查。

如果目标在第 个元素:

等概率查找成功时:

于是:

普通无序表查找失败需要真正的 次关键字比较;资料采用哨兵算法的统计口径,把哨兵位置也计入,因此写为:

考试时应结合题目给出的算法和“比较次数”定义判断。

六、有序表的顺序查找

如果表已经升序:

5  12  18  27  36  51

现在查找 25。

比较:

5 < 25
12 < 25
18 < 25
27 > 25

看到 27 时就已经可以结束。

因为后面的:

36 51 ...

只会更大。

因此有序表能够提前判失败。

失败结点

这是后面折半查找也会出现的重要思想。

对于:

10 20 30

不存在的元素被划成:

(-∞,10)
(10,20)
(20,30)
(30,+∞)

所以:

个关键字会划分出 个失败区间。

这个结论后面一定反复出现。

七、7.2.2 折半查找——★★★★★

这是第 7 章第一大核心。

资料明确强调折半查找要掌握:查找过程、判定树、查找路径、最多比较次数、成功和失败 ASL。

八、折半查找必须满足什么条件?

两个条件:

条件一:有序

例如:

2 10 25 35 40 65 70 75 81 82 88 100

条件二:必须支持随机访问

因此通常要求:

顺序存储结构。

链表虽然也能找到“中间结点”,但需要从头走过去,本身就是 ,失去了二分的意义。

因此牢记:

有序链表也不能高效折半查找。

九、折半查找算法

核心代码:

int low = 0;
int high = n - 1;

while (low <= high) {
    int mid = (low + high) / 2;

    if (A[mid] == key)
        return mid;
    else if (A[mid] > key)
        high = mid - 1;
    else
        low = mid + 1;
}
return -1;

本质就三个分支:

key = mid → 找到

key < mid → 左半部分

key > mid → 右半部分

所以每比较一次,问题规模约缩小一半。

十、用完整例子理解二分

数组:

7 10 13 16 19 29 32 33 37 41 43

查找 11。

初始:

low = 0
high = 10
mid = 5

得到:

A[5] = 29

因为:

11 < 29

所以:

high = 4

现在范围:

7 10 13 16 19

中间:

13

仍然:

11 < 13

进入:

7 10

比较 7:

11 > 7

于是最后比较 10:

11 > 10

此后:

low > high

查找失败。

这里一定把过程理解成:

每次比较不是“随便跳”,而是在维护一个可能区间。

十一、折半查找为什么是对数复杂度?

初始还有 个元素。

一次之后:

两次:

比较 次后:

所以:

得到:

因此:

十二、折半查找判定树——考试超级重要

折半查找的整个过程可以画成一棵二叉树。

例如:

1 2 3 4 5 6 7

向下取整。

第一次 mid:

4

第二层:

2      6

第三层:

1 3    5 7

所以:

        4
      /   \
     2     6
    / \   / \
   1   3 5   7

这就是折半查找判定树。

十三、判定树到底表示什么?

根:

第一次比较的元素。

第二层:

第二次可能比较的元素。

第三层:

第三次可能比较的元素。

因此某元素位于第几层:

查找它就需要比较几次。

例如 5 在第三层:

十四、失败结点

不仅成功能画出来,失败也能画。

如果有 个真实关键字,则:

所以判定树里通常画:

  • 圆形:成功结点;
  • 方形:失败结点。

查找失败时的比较次数:

等于失败结点的父结点所在层数。

因为失败结点本身不存在,并没有真正与它进行比较。

这是 ASL 失败题最容易错的地方。

十五、折半查找树的高度

资料给出:

因此最大关键字比较次数就是树高。

例如有 20 个元素:

所以需要 5 层判定树。

配套题中就直接考过“20 个结点的折半判定树,失败查找长度最小是多少”,这种题就是利用判定树层次做。

十六、折半查找 ASL 怎么算?

最可靠的方法:

不要死背总公式,直接按照判定树逐层统计。

假设某棵树:

第1层:1 个结点
第2层:2 个结点
第3层:4 个结点
第4层:3 个结点

共有 10 个元素。

成功 ASL:

得到:

这正是配套模拟题考查的形式。

一个非常典型的 12 元素题

长度为 12,采用向下取整。

各层结点数为:

第1层:1
第2层:2
第3层:4
第4层:5

所以:

即:

这就是你配套题里【王道·卷三-Q08】的计算核心。

十七、折半查找路径是否合法怎么判断?

也是高频题。

例如查 52,题目给:

80 → 22 → 76 → 25 → 37 → 52

不要只看:

一会变大、一会变小

真正的方法是:

维护 key 可能存在的上下界。

目标:

52

开始:

(-∞,+∞)

看到 80:

52 < 80

所以以后必须:

< 80

看到 22:

52 > 22

以后必须:

22 < x < 80

看到 76:

22 < 52 < 76

以后:

22 < x < 76

不断收缩区间即可。

这一技巧同样适用于:

判断 BST 查找路径是否合法。

十八、7.2.3 分块查找

也叫:

索引顺序查找。

它是顺序查找和折半查找之间的一种折中。

例如数据分 4 块。

重要条件不是:

每块内部有序。

而是:

块内可以无序,但块间必须有序。

例如:

块1:13 7 19
块2:31 22 28
块3:45 50 38

满足:

块1所有元素 < 块2所有元素
块2所有元素 < 块3所有元素

即可。

配套题也直接考这一性质。

十九、索引表

每一块建立索引项,通常记录:

该块最大关键字
+
该块起始地址

查找分两步:

key
 ↓
查索引表
 ↓
确定在哪一块
 ↓
在块内顺序查找

因此:

其中:

  • :查索引;
  • :块内查找。

二十、分块查找最优块大小

假设:

n 个元素
b 块
每块 s 个

则:

索引和块内都顺序查找时:

因为:

可得到资料中的形式。

最优块长:

最小 ASL:

因此它比线性查找:

好,但又没有折半:

那么快。

优势是动态性更好。

二十一、7.3 树形查找

进入本章第二大模块。

发展路线非常重要:

BST
↓
可能退化
↓
AVL:严格保持平衡
↓
旋转次数可能较多
↓
红黑树:放宽平衡条件
↓
插删更友好

二十二、7.3.1 二叉排序树 BST——★★★★★

BST定义:

对任意结点:

左子树所有关键字 < 根
右子树所有关键字 > 根

而左右子树本身也必须是 BST。

最重要结论:

BST 的中序遍历必定递增。

例如:

        6
       / \
      2   8
     / \
    1   4
       /
      3

中序:

1 2 3 4 6 8

二十三、BST 查找

假设查找 4:

        6
       /
      2
       \
        4

过程:

4 < 6
→ 左

4 > 2
→ 右

4 = 4
→ 成功

因此 BST 本质也是:

不断缩小 key 的可能范围。

二十四、BST 查找序列题怎么做?

目标查 363。

假设已经访问:

500

因为:

363 < 500

以后必须小于 500。

如果再访问:

200

因为:

363 > 200

以后必须处在:

200 < x < 500

接着出现任何:

x < 200

或者:

x > 500

立即不可能。

所以高频题的万能方法仍然是:

动态上下界法。

你的模拟题连续出了多道这种题。

二十五、BST 插入

BST 的插入其实就是:

先查找,查找失败的位置就是新结点应该插入的位置。

例如依次插入:

45 24 53 12

得到:

      45
     /  \
   24    53
   /
 12

新插入结点一定首先成为:

叶结点。

二十六、插入顺序决定树形

这是 BST 最核心的问题。

插入:

1 2 3 4 5

得到:

1
 \
  2
   \
    3
     \
      4
       \
        5

已经退化成链表。

查找 5 需要:

5 次比较

而如果插入顺序适当:

3 2 4 1 5

树高明显降低。

所以:

同一组关键字可以形成不同的 BST。

但:

中序遍历结果相同。

二十七、BST 删除——★★★★★

删除分三类,这个必须完全掌握。

情况 1:叶结点

直接删。

    8
   /
  3

删除 3:

8

情况 2:只有一个孩子

例如:

    8
   /
  3
 /
1

删除 3:

  8
 /
1

本质:

让孩子顶替自己。

二十八、情况 3:左右孩子都有

例如:

       50
      /  \
    30    80
         /  \
       60    90

删除 50。

不能直接把整个子树删掉。

选择:

中序前驱或中序后继替换。

中序后继:

右子树中最小的元素。

这里是:

60

所以:

50 → 60

再去原位置删除 60。

为什么这样能简化?

因为:

中序后继一定没有左孩子。

同理:

中序前驱一定没有右孩子。

于是“两个孩子”问题被转化为“零个或一个孩子”问题。

二十九、BST 的性能

如果树近似平衡:

如果退化成单支树:

资料给出的最坏等概率成功 ASL 为:

所以普通 BST 最大缺陷是:

不能保证高度。

这正是 AVL 出现的原因。

三十、为什么动态查找表更喜欢 BST?

折半查找:

查找快:

但插入、删除要移动数组元素:

BST:

平均查找:

平均插入删除:

而且只改指针。

因此资料总结为:

静态表更适合“有序顺序表 + 折半查找”;动态表更适合 BST。

三十一、7.3.2 AVL 平衡二叉树——★★★★★

AVL =

BST + 高度平衡约束。

定义平衡因子:

AVL 任意结点必须满足:

也就是:

三十二、为什么 AVL 一定快?

普通 BST 最大问题:

1
 \
 2
  \
   3
    \
     4

AVL 不允许它这样倾斜。

所以高度稳定在:

于是:

三十三、最小不平衡子树

插入一个结点后,从新结点往根方向回溯。

找到:

离新插入结点最近的,第一个平衡因子绝对值变成 2 的祖先。

记为 A。

以 A 为根的子树称:

最小不平衡子树。

考试旋转时:

只盯这个 A,不要一开始就重画整棵树。

三十四、AVL 四种旋转——必须滚瓜烂熟

最推荐的判断方法:

看从不平衡结点 A 到新结点的路径。

LL

A
/
B
/
C

路径:

左 → 左

所以 LL。

调整:

右单旋。

结果:

    B
   / \
  C   A

口诀:

LL → 右旋。

RR

A
 \
  B
   \
    C

路径:

右 → 右

调整:

左单旋。

结果:

    B
   / \
  A   C

口诀:

RR → 左旋。

三十五、LR

    A
   /
  B
   \
    C

路径:

左 → 右

不能一次解决。

先:

对 B 左旋。

再:

对 A 右旋。

即:

LR
=
先左旋
+
再右旋

三十六、RL

A
 \
  B
 /
C

路径:

右 → 左

所以:

RL
=
先右旋
+
再左旋

三十七、四种旋转一张表记住

类型插入位置操作
LL左孩子的左子树右旋
RR右孩子的右子树左旋
LR左孩子的右子树先左后右
RL右孩子的左子树先右后左

最值得记的不是名字,而是:

同向 → 单旋;反向 → 双旋。

并且:

LL 名字描述“失衡方向”,旋转方向恰好相反。

三十八、AVL 构造题应该怎么做?

例如资料中的序列:

15,3,7,10,9,8

不要一次性画。

正确流程永远是:

① 按 BST 插入
② 从新结点往上算 BF
③ 找第一个 |BF|=2
④ 判断 LL/RR/LR/RL
⑤ 旋转
⑥ 再插下一个

这是最稳妥的做法。

三十九、AVL 删除与插入最大的区别

插入造成失衡后:

通常调整最低的最小不平衡子树后,高度恢复,因此一次局部平衡调整即可。

删除不一样。

删除后某个子树可能真正变矮。

调整完 A 后:

A 子树高度又下降

可能导致 A 的父亲继续失衡。

所以:

AVL 删除可能一路向上连续调整。

资料特别强调了这一点。

四十、AVL 最少结点数——重要计算

设高度为 的 AVL 最少有 个结点。

为了“高度尽量大而结点尽量少”,左右子树高度应差 1。

所以:

初值:

于是:

……

这个数列是 AVL“最多比较次数”题的核心。

因为:

给定 ,找最大的 ,满足 。

那么查找最多比较:

四十一、7.3.3 红黑树——★★★★

红黑树可以理解为:

不追求像 AVL 那样绝对平衡,而是追求“别太歪”。

这样插入删除时调整会少很多。

四十二、红黑树五条性质

这是必须背下来的。

1

每个结点:

红 or 黑

2

根结点:

黑

3

所有 NULL 外部叶结点:

黑

4

不能有连续红结点:

红
/
红

不允许。

所以:

红结点的父亲和孩子必须是黑色。

5

从某结点到它所有后代 NULL 叶结点的路径中:

黑结点数量相同。

这就是黑高约束。

四十三、红黑树不是 AVL

这是一个超级经典陷阱。

AVL:

红黑树:

不要求这个条件。

它只保证:

所以红黑树属于:

近似平衡 BST。

不是 AVL。

四十四、红黑树高度

资料给出:

因此它虽然不如 AVL 平衡,但仍然保证:

四十五、AVL vs 红黑树——必考比较

项目AVL红黑树
平衡程度更严格较宽松
树高一般更低可能稍高
查找一般更好稍逊但仍为
插入删除调整可能更多通常调整更少
适合查找特别频繁插删也很频繁

所以:

读多写少 → AVL。

频繁插删 → 红黑树。

两者的查找、插入、删除渐近复杂度都可以达到:

配套题就直接比较过 AVL 与红黑树这些性质。

四十六、红黑树插入:理解核心即可

资料对此讲得比较细。

新结点为什么先染红?

如果新结点染黑:

所在路径立刻多一个黑结点,容易破坏“各路径黑高相同”。

染红:

黑高不变。

因此:

如果父亲是黑色:

什么都不用做。

如果父亲也是红色:

出现“红红冲突”。

此时看叔叔。

叔叔也是红色

典型策略:

父亲 → 黑
叔叔 → 黑
祖父 → 红

然后:

把祖父当作新的待调整结点继续向上。

叔叔是黑色

类似 AVL:

LL / RR / LR / RL

通过:

旋转 + 重新着色

修复。

所以理解成:

AVL 主要靠旋转;红黑树靠“染色 + 旋转”。

四十七、红黑树删除

资料专门提示:

这一部分难度较大,考查概率较低。

因此408复习优先级明显低于:

  • 五条性质;
  • 高度;
  • AVL vs 红黑树;
  • 红黑树插入基本思想。

删除只需要先理解一个核心:

删红色通常容易,删黑色容易导致某些路径少一个黑结点。

于是引入“双黑”思想,通过:

  • 兄弟颜色;
  • 兄弟孩子颜色;
  • 旋转;
  • 重新着色;

逐步消除双黑。

若时间有限,不建议在第一轮复习里死背红黑树删除全部情况。

四十八、7.4 B 树——本章最大难点之一 ★★★★★

B树出现的根本原因是:

二叉树虽然 CPU 比较次数不多,但如果数据在磁盘上,树太高意味着磁盘 I/O 太多。

磁盘 I/O 比 CPU 比较慢太多。

于是:

二叉树
每结点只有2条分支

改成:

一个结点存多个 key
有很多条分支

于是树变得非常矮。

这就是 B 树。

四十九、m 阶 B 树是什么意思?

重点:

表示一个结点最多拥有的子树数。

所以:

最大子树数:

最大关键字数:

原因:

如果一个结点有:

K1 K2 K3

它把数值区间划分成:

< K1
K1~K2
K2~K3
> K3

三个关键字产生四棵子树。

因此:

这是 B 树最基础的关系。

五十、m 阶 B 树结点关键字数范围

除根之外的非叶结点至少:

棵子树。

所以至少关键字:

最多:

即非根结点:

根如果不是叶结点,至少:

2 棵子树

也就是至少:

1 个关键字

五十一、B 树为什么有序?

一个结点:

P0 K1 P1 K2 P2 ... Kn Pn

关键字满足:

其中:

P0 子树 < K1

P1 子树在 K1 和 K2 之间

...

Pn 子树 > Kn

这就是 BST 的多路版本。

BST:

< root | > root

B 树:

<K1
K1~K2
K2~K3
...
>Kn

五十二、关于“叶结点”的口径要特别注意

你这份资料采用王道常见描述:

最底层的“叶结点”是外部失败结点,不实际存储关键字。

而真正最低一层存储关键字的结点常称:

终端结点。

所以看到题目说:

“B树所有叶结点在同一层”

一定先看题目采用的是哪套定义。

本资料的口径下:

终端结点
↓
外部叶结点(空指针、失败结点)

五十三、B 树查找

例如某结点:

20 | 50 | 80

查 63。

比较之后知道:

50 < 63 < 80

所以进入:

50 与 80 之间的那棵子树

进入下一结点继续。

因此一次查找过程包括:

磁盘读一个结点
↓
内存里查该结点的若干关键字
↓
找到下一条子树指针
↓
再读下一结点

由于磁盘访问昂贵:

B树最关注的是树高,而不是结点内多比较几次。

五十四、B 树高度计算——高频★★★★★

假设:

n 个关键字
m 阶 B树
高度 h

资料中的高度:

不包括最下面的外部失败叶结点。

最小高度

想让树最矮:

每个结点尽可能装满。

每个结点最多:

个关键字。

高度为 时最大关键字:

化简:

所以:

整数题再向上取整。

五十五、B 树最大高度

想让树最高:

每个结点尽可能少装关键字。

除根外每个非叶结点至少:

个孩子。

资料推导:

于是:

这是408非常常见的选择题公式。

你的配套题中就直接考了:

“含 个关键字的 阶 B 树最大高度”。

五十六、B 树插入——★★★★★

B树插入要牢记一句:

先插到最底层终端结点;满了就分裂,中间关键字上升。

步骤如下:

① 按 B 树规则查找插入位置
② 插入关键字
③ 判断结点是否溢出
④ 若溢出,分裂
⑤ 中间关键字上升到父结点
⑥ 父结点若又溢出,继续向上分裂

五十七、什么时候溢出?

m 阶 B 树一个结点最多:

个关键字。

插入以后如果有:

个关键字:

溢出。

需要分裂。

五十八、B 树分裂怎么做?

假设插入后关键字为:

K1 K2 ... Km

资料采用:

位置的关键字上升。

它左边:

留在原结点。

右边:

放入新结点。

中间关键字:

插入父结点。

5 阶 B 树举例

最大:

个关键字。

若插入后:

10 20 30 40 50

出现 5 个关键字。

中间:

30

上升。

得到:

        30
       /  \
  10 20    40 50

如果父亲也满:

父亲再分裂。

如果一路传到根:

根也分裂。

这时会创建一个新根:

注意:

B树高度增长发生在根部,不是底部。

这是经典陷阱。

五十九、B 树删除——★★★★★

比插入稍难,但其实只需要抓住:

删完不能低于最少关键字数。

除根之外最少:

六十、第一步:如果关键字在非终端结点

例如:

        50
       /  \
   ...    ...

删除 50。

先找:

  • 中序前驱;或
  • 中序后继。

用它替代 50。

然后:

去终端结点删除那个前驱/后继。

所以最终所有删除问题都可以归约成:

删除终端结点中的关键字。

这和 BST 删除思想高度相似。

六十一、终端结点删除三种情况

情况 1:删完仍然够

如果删除前关键字数至少为:

删除一个以后仍然至少:

所以:

直接删。

六十二、情况 2:自己不够,但兄弟够借

假设:

当前结点关键字太少
右兄弟很多

结构:

       [父关键字 P]
       /          \
 当前结点        右兄弟

不能直接把兄弟元素横着搬过来,因为会破坏有序区间。

正确操作:

父关键字 P 下来
兄弟边缘关键字上去

例如右兄弟够借:

父亲 71
右兄弟 74 ...

那么:

71 ↓ 当前结点
74 ↑ 父亲

所以本质是:

通过父结点中转。

六十三、情况 3:兄弟也不够借

那就:

合并。

结构:

左结点
+
父亲分隔关键字
+
右结点

合并成一个结点。

父亲因此少一个关键字。

问题是:

父亲可能因此也不够。

于是:

删除不足可能向上传播。

这和 AVL 删除的“向上连锁调整”很像。

六十四、根结点的特殊情况

如果合并后:

根已经 0 个关键字,只剩一个孩子。

那么:

删除原根,让唯一孩子成为新根。

于是:

所以 B 树:

  • 插入导致高度增加:发生在根分裂;
  • 删除导致高度减少:发生在根被合并掉。

六十五、7.4.2 B+ 树——理解为主

资料明确说明:

B 树需要掌握基本操作;B+ 树主要考基本概念。

B+ 树是数据库索引里非常重要的结构。

六十六、B+ 树最核心的设计

B 树:

数据可能存放在任何层。

B+ 树:

真正的数据记录全部放在叶结点。

非叶结点只做:

索引。

因此:

        索引
       /    \
     索引    索引
      ↓       ↓
   叶结点—叶结点—叶结点
       真正的数据

六十七、B+ 树五个核心性质

按照本资料的口径:

  1. 分支结点最多 棵子树;
  2. 非根分支结点至少有:

棵子树;

  1. 一个分支结点的关键字个数 = 子树数;
  2. 所有关键字和记录指针最终都出现在叶结点;
  3. 叶结点按关键字有序,并互相链接。

六十八、B树 vs B+树——★★★★★

这张表务必掌握。

对比B树B+树
有 个关键字 棵子树 棵子树
数据位置各层均可能全在叶结点
非叶关键字真正记录,只出现一次索引副本
查到非叶相同 key可以成功结束还要继续向叶结点
叶结点链表通常没有有
范围查找较不方便非常方便
数据库索引可用更常见

六十九、为什么数据库喜欢 B+ 树?

原因 1:非叶结点更小

B+ 树非叶结点不用存完整数据记录,只需要:

key + child pointer

所以一个磁盘页可以塞更多索引项。

分支因子更大:

树更矮。

磁盘 I/O 更少。

原因 2:范围查询非常强

例如查:

100 ≤ key ≤ 500

先通过树找到 100。

之后:

100 → 101 → ... → 500

沿叶结点链表向右扫就行。

所以数据库特别喜欢 B+ 树。

七十、B+树查找的一个经典区别

B 树:

如果内结点已经找到 key,就可以成功。

B+ 树:

即使内结点 key 相等,也只是索引,还必须继续走到叶结点。

因此:

这也是选择题高频判断。

七十一、7.5 散列表 Hash——★★★★★

进入本章第三个超级重点。

前面的所有算法都需要不断:

比较 key
↓
缩小搜索范围

散列表想做一件更激进的事:

能不能直接由 key 算出它在哪?

定义:

例如:

如果:

key = 27

得到:

那么直接访问地址 1。

理想情况下:

七十二、冲突与同义词

例如:

那么:

同时:

两个不同 key 得到同一个地址。

这叫:

散列冲突。

14 和 27 叫:

同义词。

必须特别明确:

冲突不是程序设计错误,而是哈希中正常且基本无法彻底消除的问题。

真正要解决的是:

冲突以后放到哪里。

七十三、散列函数的构造

资料讲四种。

1. 直接定址法

或者:

优点:

不发生冲突。

缺点:

key 范围大、数据稀疏时特别浪费空间。

七十四、2. 除留余数法——最重要

通常:

取不超过表长、并尽可能接近表长的质数。

目的是尽量均匀。

这是考试中最常见的散列函数。

七十五、3. 数字分析法

例如电话号码、身份证号等。

不同位的分布可能不同。

如果某几位:

0~9 出现得比较均匀

就选这些位组成地址。

适用于:

关键字集合预先已知且较稳定。

七十六、4. 平方取中法

先:

然后取中间几位。

因为平方结果的中间位往往同时受 key 多个位影响,所以分布可能更均匀。

七十七、哈希冲突处理——★★★★★

两大类:

开放定址法

和:

拉链法

七十八、开放定址法

核心:

冲突后,继续在散列表中寻找其他空位。

公式:

区别只在:

怎么选。

七十九、线性探测法

取:

例如:

5 占了。

查:

6
7
8
9
...

到表尾后回到 0。

所以就是:

一路向后找空位。

优点:

只要表没满,一定能找到空位。

缺点非常重要:

堆积 / 聚集。

八十、什么叫堆积?

假设:

5 6 7 8

已经连续被占。

一个本来散列到 5 的元素:

5 占
6 占
7 占
8 占
→ 放9

以后本来散列到 6 的元素又要:

6 7 8 9 ...

于是形成越来越长的连续区域。

结果:

冲突越来越严重。

这个现象就是:

一次聚集。

你的模拟题明确覆盖了这一类冲突探测题。

八十一、平方探测法

资料给出的增量:

于是探测:

H+1
H-1
H+4
H-4
H+9
H-9
...

相比线性探测:

不再只挤在连续位置。

因此能缓解堆积。

按照本资料口径,使用该形式时表长 取:

形如 的素数。

八十二、双散列法

使用两个散列函数。

第一散列:

决定起点。

第二散列:

决定步长。

于是:

直观理解:

每个 key 都可能有不同的跳跃步长。

比线性探测更不容易形成大堆积。

八十三、伪随机探测

让:

采用伪随机序列。

考试知道思想即可。

八十四、开放定址法删除——经典陷阱★★★★★

假设:

H(A)=5

但:

位置5被B占用

所以 A 经探测存到:

6

现在把 B 直接从 5 删除:

位置5变空

以后查 A:

先查5

发现:

空!

算法会认为:

A 不存在。

但实际上 A 在 6。

所以开放定址法:

需要设置:

删除标记。

查找经过删除标记:

必须继续。

真正的从未使用空位置:

才可以说明失败。

这是非常高频的判断题。

八十五、拉链法

把相同散列地址的元素放到链表。

例如:

如果:

14 → 1
27 → 1
40 → 1

就:

HT[1]
 ↓
14 → 27 → 40

优点:

  • 不需要在整个数组中四处探测;
  • 插入删除方便;
  • 特别适合动态表。

八十六、为什么拉链法装填因子可以大于 1?

装填因子:

其中:

n = 实际记录数
m = 哈希地址数

开放定址:

每个数组位置最多放一个元素。

因此:

所以:

拉链法:

一个桶可以挂很多结点。

因此:

完全可能。

于是:

也是可能的。

你的王道配套题就直接问了这一点。

八十七、哈希查找过程

假设:

采用线性探测。

查 84:

若:

6:19

不等。

继续:

7:20

不等。

继续:

8:84

成功。

于是比较次数:

八十八、Hash 成功 ASL 怎么算?——★★★★★

这是本章计算题最重要的方法之一。

原则:

每一个已有关键字,从它自己的初始散列地址重新模拟查找,数比较了几次。

例如:

A:1次
B:1次
C:3次
D:2次

那么:

即:

千万不要看它最终所在下标直接算。

应该问:

“查找它时从 Hash(key) 开始,需要探测几个位置?”

八十九、Hash 失败 ASL 怎么算?——最容易错★★★★★

开放定址法中:

对每一个可能的初始散列地址,模拟一个不存在的 key,从该地址开始探测,直到碰到真正空位置。

注意:

真正空位置本身也要探测,因此计入查找长度。

例如某次:

起点4:占
5:占
6:占
7:空

那么失败比较/探测长度:

不是 3。

因为要访问地址 7 后才能确认:

不存在。

九十、还有一个大坑:到底平均几个初始地址?

如果:

那么散列函数只可能产生:

0~12

即 13 个初始地址。

即使物理散列表长度是:

16

失败 ASL 也应首先根据题目散列函数的可能初始地址范围分析,而不是机械地除以 16。

这类题一定先看:

Hash 函数值域。

九十一、拉链法的失败 ASL

采用“关键字比较次数”口径时:

一个不存在 key 散到某条链上,需要把该链已有结点全部比较完。

若各桶链长:

0, 2, 0, 1, 3, 0, 1

失败 ASL:

因此拉链法中:

空链查找失败可以是 0 次关键字比较。

你配套题的拉链法失败 ASL 正是在考这种统计方式。

九十二、影响散列表性能的三个核心因素

资料明确归纳为:

1 散列函数

分布是否均匀。

2 冲突处理方法

线性探测、平方探测、拉链等效果不同。

3 装填因子

一般:

越大,表越拥挤,冲突越多。

因此散列表 ASL:

不是只由表长决定。

也不是:

“只要 Hash 就一定 O(1)”。

准确说法是:

在散列函数合理、装填因子受控的情况下,平均查找复杂度可以接近 。

九十三、整章 ASL 统一起来理解

这是这一章最重要的抽象。

所有 ASL 本质都在做同一件事:

区别只是:

结构比较次数从哪里来?
顺序查找元素离起点多远
折半查找判定树层数
BST结点深度
AVL结点深度
B树主要体现为访问层数
Hash探测次数或链表比较次数

资料最后也把 ASL 作为整章的统一核心进行归纳。

九十四、成功和失败 ASL 要严格区分

成功:

失败:

个有序关键字:

通常对应 个失败区间。

如果分别假设成功元素等概率:

失败区间等概率:

不能把两个分母混在一起。

九十五、408最常见的五类题,你应该怎么做

第一类:折半查找

看到:

  • 查找序列;
  • 比较次数;
  • 判定树;
  • ASL;
  • 查找失败。

第一反应:

画判定树。

特别是 ASL:

不要凭感觉,直接数每层结点数。

第二类:BST 路径是否合法

看到:

查找 key = x
访问序列 ...

第一反应:

维护上下界。

每访问一个结点,就缩小 key 所在区间。

比死画 BST 快得多。

第三类:AVL 插入

第一反应:

普通 BST 插入
↓
找最低 |BF|=2
↓
看路径 LL/RR/LR/RL
↓
旋转

不要直接猜旋转。

第四类:B 树插删

插入只问:

满了吗?
↓
满了
↓
中间关键字上升
↓
分裂

删除只问:

删完够吗?
├─够 → 直接删
└─不够
   ├─兄弟够 → 借
   └─兄弟不够 → 合并

把这棵“决策树”背下来,B树删除就不会乱。

第五类:Hash

永远按真实过程画表:

算 H(key)
↓
冲突?
↓
按探测规则找
↓
填入表

然后:

成功 ASL:

从每个元素自己的 Hash 起点重新找一次。

失败 ASL:

从每个可能起点走到第一个真正空位置。

九十六、最容易混淆的概念

易混点正确结论
有序链表能否高效折半?不能
折半查找需要什么?有序 + 随机访问
BST一定 ?不一定,最坏
AVL中所有叶结点高度差是否 ≤1?不要求
红黑树是不是AVL?不是
红黑树查找一定比AVL快?不是
B树的阶 是什么?最大子树数
B树 个关键字有几棵子树?
B+树 个关键字有几棵子树?
B+树数据在哪里?叶结点
B+树内结点命中能否结束?不能,要到叶结点
Hash 是否完全没有比较?不是,冲突后仍需比较
开放定址能否直接删除?不能
拉链法装填因子能否大于1?能
Hash一定 ?平均情况下才接近

九十七、最后给你一个“第7章主线”

如果把这一章压缩成一句话:

查找效率取决于你能利用多少已有结构信息。

顺序查找:

不知道任何东西
→ 一个一个找

折半查找:

知道有序
→ 每次砍掉一半

BST:

把有序关系变成树
→ 动态插删更方便

AVL:

防止 BST 长歪
→ 强制高度 O(log n)

红黑树:

稍微放松平衡
→ 换取更低的更新代价

B/B+树:

一次结点访问看很多 key
→ 大幅降低磁盘树高

Hash:

直接建立 key→地址映射
→ 平均接近 O(1)

九十八、按考研优先级复习

如果你准备408,建议第7章按这个顺序掌握:

第一梯队,必须做到会手算:

折半查找判定树与成功/失败 ASL BST 查找、插入、删除与合法查找路径 AVL 四种旋转 B 树定义、阶数、高度、插入、删除 Hash 构造、线性/平方探测、拉链法、成功/失败 ASL

第二梯队,要理解并会选择题:

分块查找 AVL 最少结点递推 红黑树五条性质及 AVL 对比 B树和 B+树区别 装填因子和哈希性能

第三梯队,第一轮可以降低投入:

红黑树复杂删除调整 B+树具体插入删除实现 较冷门散列函数细节

这与资料本身标出的复习重点基本一致:折半查找、树形查找、B树、散列查找构成本章主干,而红黑树删除和 B+ 树深入操作的优先级明显更低。

如果只记住第 7 章最重要的四句话,就是:

折半查找:画判定树。 AVL:找最低失衡点,看 LL/RR/LR/RL。 B树:插入满了就分裂,删除少了就借或合并。 Hash:先完整构造表,再按真实探测过程算 ASL。

On this page

一、先建立整章框架二、7.1 查找基本概念:重点是 ASL1. 查找、查找表、关键字2. 静态查找表和动态查找表三、全章灵魂:平均查找长度 ASL四、7.2 顺序查找1. 普通顺序查找2. 哨兵技巧五、顺序查找的 ASL六、有序表的顺序查找失败结点七、7.2.2 折半查找——★★★★★八、折半查找必须满足什么条件?条件一:有序条件二:必须支持随机访问九、折半查找算法十、用完整例子理解二分十一、折半查找为什么是对数复杂度?十二、折半查找判定树——考试超级重要十三、判定树到底表示什么?十四、失败结点十五、折半查找树的高度十六、折半查找 ASL 怎么算?一个非常典型的 12 元素题十七、折半查找路径是否合法怎么判断?十八、7.2.3 分块查找十九、索引表二十、分块查找最优块大小二十一、7.3 树形查找二十二、7.3.1 二叉排序树 BST——★★★★★二十三、BST 查找二十四、BST 查找序列题怎么做?二十五、BST 插入二十六、插入顺序决定树形二十七、BST 删除——★★★★★情况 1:叶结点情况 2:只有一个孩子二十八、情况 3:左右孩子都有二十九、BST 的性能三十、为什么动态查找表更喜欢 BST?三十一、7.3.2 AVL 平衡二叉树——★★★★★三十二、为什么 AVL 一定快?三十三、最小不平衡子树三十四、AVL 四种旋转——必须滚瓜烂熟LLRR三十五、LR三十六、RL三十七、四种旋转一张表记住三十八、AVL 构造题应该怎么做?三十九、AVL 删除与插入最大的区别四十、AVL 最少结点数——重要计算四十一、7.3.3 红黑树——★★★★四十二、红黑树五条性质12345四十三、红黑树不是 AVL四十四、红黑树高度四十五、AVL vs 红黑树——必考比较四十六、红黑树插入:理解核心即可叔叔也是红色叔叔是黑色四十七、红黑树删除四十八、7.4 B 树——本章最大难点之一 ★★★★★四十九、m 阶 B 树是什么意思?五十、m 阶 B 树结点关键字数范围五十一、B 树为什么有序?五十二、关于“叶结点”的口径要特别注意五十三、B 树查找五十四、B 树高度计算——高频★★★★★最小高度五十五、B 树最大高度五十六、B 树插入——★★★★★五十七、什么时候溢出?五十八、B 树分裂怎么做?5 阶 B 树举例五十九、B 树删除——★★★★★六十、第一步:如果关键字在非终端结点六十一、终端结点删除三种情况情况 1:删完仍然够六十二、情况 2:自己不够,但兄弟够借六十三、情况 3:兄弟也不够借六十四、根结点的特殊情况六十五、7.4.2 B+ 树——理解为主六十六、B+ 树最核心的设计六十七、B+ 树五个核心性质六十八、B树 vs B+树——★★★★★六十九、为什么数据库喜欢 B+ 树?原因 1:非叶结点更小原因 2:范围查询非常强七十、B+树查找的一个经典区别七十一、7.5 散列表 Hash——★★★★★七十二、冲突与同义词七十三、散列函数的构造1. 直接定址法七十四、2. 除留余数法——最重要七十五、3. 数字分析法七十六、4. 平方取中法七十七、哈希冲突处理——★★★★★七十八、开放定址法七十九、线性探测法八十、什么叫堆积?八十一、平方探测法八十二、双散列法八十三、伪随机探测八十四、开放定址法删除——经典陷阱★★★★★八十五、拉链法八十六、为什么拉链法装填因子可以大于 1?八十七、哈希查找过程八十八、Hash 成功 ASL 怎么算?——★★★★★八十九、Hash 失败 ASL 怎么算?——最容易错★★★★★九十、还有一个大坑:到底平均几个初始地址?九十一、拉链法的失败 ASL九十二、影响散列表性能的三个核心因素1 散列函数2 冲突处理方法3 装填因子九十三、整章 ASL 统一起来理解九十四、成功和失败 ASL 要严格区分九十五、408最常见的五类题,你应该怎么做第一类:折半查找第二类:BST 路径是否合法第三类:AVL 插入第四类:B 树插删第五类:Hash九十六、最容易混淆的概念九十七、最后给你一个“第7章主线”九十八、按考研优先级复习