考研复习 Day 20 | 数据结构与算法--查找

一、查找的基本概念

术语 定义
查找 在数据集合中寻找满足某种条件的数据元素的过程
查找表 用于查找的数据集合
静态查找表 仅涉及查找操作,无须动态修改(顺序查找、折半查找)
动态查找表 需要支持插入或删除操作(二叉排序树查找)
关键字 数据元素中唯一标识该元素的数据项
平均查找长度(ASL) ASL = Σ Pᵢ·Cᵢ,衡量查找算法效率的核心指标

二、顺序查找和折半查找

2.1 顺序查找(线性查找)

适用于顺序表和链表,可用于无序表和有序表。

哨兵技巧:将ST.elem[0]设为哨兵,从后往前查找,避免循环中反复判断数组下标是否越界。

场景 成功ASL 失败ASL
无序表(等概率) (n+1)/2 n+1
有序表(等概率) (n+1)/2 n/2 + n/(n+1)(优于无序表)

优化技巧:若已知各记录查找概率,应将记录按查找概率由大到小排列,使高频元素靠近查找起点。


2.2 折半查找(二分查找)

仅适用于关键字有序的顺序表(不支持链式存储)。

复制代码
int BinarySearch(SSTable L, ElemType key) {
    int low = 0, high = L.TableLen - 1, mid;
    while (low <= high) {
        mid = (low + high) / 2;
        if (L.elem[mid] == key) return mid;
        else if (L.elem[mid] > key) high = mid - 1;
        else low = mid + 1;
    }
    return -1;
}

判定树:平衡二叉树,树高h = ⌈log₂(n+1)⌉

指标
成功ASL ≈ log₂(n+1) - 1
时间复杂度 O(log₂n)

注意:折半查找仅适用于顺序存储结构,且要求表中元素按关键字有序排列。


2.3 分块查找(索引顺序查找)

基本思想:将查找表划分为若干子块,块内可无序,块间有序(前一块的最大关键字 < 后一块的所有关键字)。建立索引表(最大关键字 + 起始地址)。

查找过程:

  1. 在索引表中确定待查记录所在的块(顺序查找或折半查找)

  2. 在目标块内进行顺序查找

ASL = 索引查找长度 + 块内查找长度

当均匀分块(n(总记录数)=b(块数)×s(每块大小)),索引表和块内均采用顺序查找时:

  • ASL = (b+1)/2 + (s+1)/2 = (s²+2s+n)/(2s)

  • 最优块大小 s = √n 时,ASL_min = √n + 1


三、树形查找

3.1 二叉排序树(BST)

定义:左子树所有结点 < 根结点 < 右子树所有结点,左右子树也分别为BST。中序遍历得到递增序列。

基本操作:

操作 实现要点
查找 从根开始,逐层向下比较,选择左/右子树
插入 查找失败的位置即为插入位置,新结点必为叶结点
构造 从空树开始,依次插入元素(重复关键字被忽略)
删除 ①叶结点直接删;②仅有一棵子树,子树上移;③有两棵子树,用中序后继(或前驱)替代,转化为①或②

查找效率:

  • 平均:O(log₂n)

  • 最坏:输入序列有序 → 单支树 → O(n)

与二分查找对比:二分查找的判定树唯一,BST的形态取决于插入顺序。动态查找(频繁插入/删除)宜用BST,静态查找宜用二分查找。


3.2 平衡二叉树(AVL树)

定义:任意结点的左右子树高度差的绝对值 ≤ 1。平衡因子 = 左高 - 右高,取值 -1、0、1。

插入后的四种调整:

情况 描述 调整操作
LL 在A的左孩子的左子树插入 右单旋转
RR 在A的右孩子的右子树插入 左单旋转
LR 在A的左孩子的右子树插入 先左旋后右旋
RL 在A的右孩子的左子树插入 先右旋后左旋

性质:

  • 查找时间复杂度:O(log₂n)

  • 删除可能需要多次调整(向上回溯)

  • 含n个结点的AVL树最大深度为O(log₂n)


3.3 红黑树

红黑性质:
① 每个结点红或黑;② 根黑;③ 叶结点(NULL)黑;④ 无两个相邻红结点;⑤ 每个结点到任意叶结点的路径上黑结点数相同。

结论:

  • 从根到叶的最长路径 ≤ 2 × 最短路径

  • 有n个内部结点的红黑树高度 h ≤ 2log₂(n+1)

插入:新结点初始着为红色。根据叔结点颜色分三种情形调整。

与AVL对比:

场景 推荐
查找操作远多于插入/删除 AVL树
插入/删除频繁 红黑树

实际应用:C++的map/set、Java的TreeMap/TreeSet均基于红黑树实现。


四、B树和B+树

4.1 B树(多路平衡查找树)

m阶B树:每个结点至多m棵子树,所有叶结点在同一层次。

性质(根结点特殊):

结点类型 子树范围 关键字范围
根结点(非叶) 2 ~ m 1 ~ m-1
其他非叶结点 ⌈m/2⌉ ~ m ⌈m/2⌉-1 ~ m-1
叶结点 无子树 不存储数据

B树的高度(n个关键字):

  • 最小高度:h ≥ log_m(n+1)

  • 最大高度:h ≤ log_{⌈m/2⌉}((n+1)/2) + 1

插入:结点关键字数达m时分裂 --- 中间关键字提升至父结点,左右分裂为两个结点。

删除:需保证结点关键字数 ≥ ⌈m/2⌉-1,否则需借位或合并。


4.2 B+树

m阶B+树:所有关键字和记录指针存储在叶结点中,叶结点通过指针按关键字顺序链接。

B树 vs B+树:

对比项 B树 B+树
关键字与子树关系 n个关键字 → n+1棵子树 n个关键字 → n棵子树
关键字范围 ⌈m/2⌉-1 ~ m-1 ⌈m/2⌉ ~ m
关键字存储 所有结点 仅叶结点
非叶结点功能 存储关键字 仅索引(存储子结点最大值)
支持顺序查找 ✅(叶结点链表)

B+树常用于数据库索引,支持两种查找:顺序查找(从头指针)和多路查找(从根结点)。


五、散列表(Hash表)

5.1 基本概念

散列函数:Hash(key) = Addr,将关键字映射到存储地址。
冲突:不同关键字映射到同一地址,这些关键字称为同义词。


5.2 散列函数的构造方法

方法 公式 特点
直接定址法 H(key)=keya·key+b 无冲突,适用于关键字连续分布
除留余数法 H(key)=key % p 最常用,p为不超过m且接近m的质数
数字分析法 选取分布均匀的数位 适用于已知且固定的关键字集合
平方取中法 取关键字平方的中间几位 分布均匀,适用于位数较少的情形

5.3 处理冲突的方法

1. 开放定址法

Hᵢ = (H(key) + dᵢ) % m

方法 增量序列 特点
线性探测 dᵢ = 1,2,...,m-1 易产生聚集(堆积)
平方探测 dᵢ = 1²,-1²,2²,-2²,... 避免堆积,m需为4k+3的素数
双散列 dᵢ = i·H₂(key) 需两个散列函数
伪随机序列 dᵢ = 第i个伪随机数 ---

重要:开放定址法不能直接物理删除元素,否则会截断其他同义词的查找路径。应设置删除标记实现逻辑删除。

2. 拉链法(链地址法)

将散列到同一地址的所有同义词组织成一个链表。适用于频繁插入/删除的场景。


5.4 散列查找性能

装填因子:α = 表中记录数 / 散列表长度,α越大,冲突概率越高。

影响ASL的三个因素:

  1. 散列函数

  2. 冲突处理方法

  3. 装填因子α

六、查找算法具体例子与图示

1、顺序查找

例子:在无序表中查找关键字 7

数据:[5, 3, 8, 1, 9, 2, 7, 4, 6](共9个元素)

查找过程:

索引: 0 1 2 3 4 5 6 7 8
值: [5] [3] [8] [1] [9] [2] [7] [4] [6]

第1次比较: 5≠7,继续

5\] \[3\] \[8\] \[1\] \[9\] \[2\] \[7\] \[4\] \[6


第2次比较: 3≠7,继续

5\] \[3\] \[8\] \[1\] \[9\] \[2\] \[7\] \[4\] \[6


第3次比较: 8≠7,继续

... 跳过中间 ...

5\] \[3\] \[8\] \[1\] \[9\] \[2\] \[7\] \[4\] \[6


第6次比较: 2≠7,继续

5\] \[3\] \[8\] \[1\] \[9\] \[2\] \[7\] \[4\] \[6


第7次比较: 7=7 查找成功,位置6

图示:

顺序查找过程(查找7):

查找成功ASL = (1+2+...+9)/9 = 5
查找失败ASL = 10(需比较所有9个元素+哨兵)


2、折半查找

例子:在有序表中查找关键字 33

数据:[7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43](11个元素)

查找过程:

初始: low = 0, high = 10

7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43

↑ ↑
low high

第1次: mid = (0+10)/2 = 5 → 29

7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43


mid
33 > 29 → 在右半部分,low = mid+1 = 6

第2次: low=6, high=10, mid = (6+10)/2 = 8 → 37

7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43

↑ ↑ ↑
low mid high
33 < 37 → 在左半部分,high = mid-1 = 7

第3次: low=6, high=7, mid = (6+7)/2 = 6 → 32

7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43

↑ ↑
(low) mid high
33 > 32 → 在右半部分,low = mid+1 = 7

第4次: low=7, high=7, mid = (7+7)/2 = 7 → 33

7, 10, 13, 16, 19, 29, 32, 33, 37, 41, 43


(low) mid (high)
33 = 33 → 查找成功,位置7

图示(判定树):

折半查找判定树(n=11):

29
/ \
13 37
/ \ / \
7 16 32 41
\ \ \ \
10 19 33 43

(此处省略失败结点)

查找33的路径: 29 → 37 → 32 → 33(共4次比较)

ASL成功 = (1×1 + 2×2 + 3×4 + 4×4)/11 = 3
ASL失败 = (3×4 + 4×8)/12 ≈ 3.67


3、分块查找

例子:查找关键字 62

数据:[22, 12, 13, 9, 8, 33, 42, 44, 38, 24, 48, 60, 58, 74, 49, 86, 53]

分块规则:按值域分为4块

  • 第1块:最大值 22(22,12,13,9,8)

  • 第2块:最大值 48(33,42,44,38,24,48)

  • 第3块:最大值 60(60,58)

  • 第4块:最大值 86(74,49,86,53)

索引表:

查找过程:

第1步:在索引表中确定块
索引表: [22, 48, 60, 86]
↑ ↑
22<62 ? 48<62 ? 60<62 ? 86>62
62 > 60,62 < 86 → 在第4块

第2步:在第4块内顺序查找
块4: [74, 49, 86, 53]
↑ ↑ ↑ ↑
1 2 3 4
74≠62, 49≠62, 86≠62, 53≠62 → 查找失败

图示:

分块查找结构:

查找62: 索引表找到块4 → 块内顺序查找 → 未找到


4、二叉排序树(BST)

例子:构造BST并查找关键字 50

插入序列:[50, 30, 80, 20, 40, 70, 90, 35, 85]

构造过程:

(1) 插入50 (2) 插入30
50 50
/ \ / \
30

(3) 插入80 (4) 插入20
50 50
/ \ / \
30 80 30 80
/
20

(5) 插入40 (6) 插入70
50 50
/ \ / \
30 80 30 80
/ \ / \ /
20 40 20 40 70

(7) 插入90 (8) 插入35
50 50
/ \ / \
30 80 30 80
/ \ / \ / \ / \
20 40 70 90 20 40 70 90
/
35

(9) 插入85
50
/ \
30 80
/ \ / \
20 40 70 90
/ /
35 85

查找50的过程:

查找50:
50 ← 第1次比较: 50=50 查找成功
/ \
30 80
/ \ / \
20 40 70 90
/ /
35 85

比较次数: 1

查找35的过程:

查找35:
50 ← 第1次比较: 35<50,向左
/ \
30 80 ← 第2次比较: 35>30,向右
/ \ / \
20 40 70 90 ← 第3次比较: 35<40,向左
/ /
35 85 ← 第4次比较: 35=35


5、平衡二叉树(AVL)

例子:构造AVL树并演示LL旋转

插入序列:[30, 20, 10]

构造过程:

(1) 插入30 (2) 插入20
30 30
/ \ / \
20
平衡因子: 30:0 平衡因子: 30:1, 20:0

(3) 插入10
3
/ \
20
/
10

此时平衡因子: 30:2 不平衡!
插入位置: 30的左孩子的左子树 → LL型

LL旋转(右单旋):

旋转前: 旋转后:
30 20
/ \ / \
20 X 10 30
/ \ / \
10 Y Y X

其中X、Y为可能存在的子树

具体旋转:

旋转前: 旋转后:
30 20
/ / \
20 10 30
/
10

平衡因子: 30:2, 20:1, 10:0 → 20:0, 30:0, 10:0


6、B树

例子:3阶B树(每个结点最多2个关键字,最少1个)

插入序列:[50, 30, 80, 20, 40, 70, 90]

构造过程:

(1) 插入50 (2) 插入30

50\] \[30,50

(3) 插入80

30,50,80\] → 结点满(3个关键字),分裂 中间关键字50提升为根 \[50

/ \

30\] \[80

(4) 插入20

50

/ \

20,30\] \[80

(5) 插入40

50

/ \

20,30,40\] \[80\] → 左孩子满,分裂 中间关键字30提升,插入父结点 \[30,50

/ | \

20\] \[40\] \[80

(6) 插入70

30,50

/ | \

20\] \[40\] \[70,80

(7) 插入90

30,50

/ | \

20\] \[40\] \[70,80,90\] → 右孩子满,分裂 中间关键字80提升 \[30,50,80

/ | | \

20\] \[40\] \[70\] \[90

B树图示(最终形态):

30,50,80

/ | \ \

20\] \[40\] \[70\] \[90

(叶结点) (叶) (叶) (叶) (叶)

查找70: 根节点30<50<70<80 → 第3棵子树 → 找到70


7、散列表

例子:拉链法

关键字序列:[19, 14, 23, 1, 68, 20, 84, 27, 55, 11, 10, 79]
散列函数:H(key) = key % 13

散列表结构:

索引 0: 1 → 14 → 27 → 79 → 53
索引 1: (空)
索引 2: 2 → 15 → 28 → 41 → 54
索引 3: 3 → 16 → 29 → 42 → 55
索引 4: 4 → 17 → 30 → 43 → 56
索引 5: 5 → 18 → 31 → 44 → 57
索引 6: 6 → 19 → 32 → 45 → 58
索引 7: 7 → 20 → 33 → 46 → 59
索引 8: 8 → 21 → 34 → 47 → 60
索引 9: 9 → 22 → 35 → 48 → 61
索引 10: 10 → 23 → 36 → 49 → 62
索引 11: 11 → 24 → 37 → 50 → 63
索引 12: 12 → 25 → 38 → 51 → 64

查找84的过程:

H(84) = 84 % 13 = 6
索引6的链表: 6 → 19 → 32 → 45 → 58 → 71 → 84
比较: 6≠84, 19≠84, 32≠84, 45≠84, 58≠84, 71≠84, 84=84
比较次数: 7次

例子:线性探测法

同一关键字序列,散列表长度16

构造过程:

H(19)=6: L[6]=19
H(14)=1: L[1]=14
H(23)=10: L[10]=23
H(1)=1: 冲突 → L[2]=1
H(68)=3: L[3]=68
H(20)=7: L[7]=20
H(84)=6: 冲突 → L[8]=84
H(27)=1: 冲突 → L[4]=27
H(55)=3: 冲突 → L[5]=55
H(11)=11: L[11]=11
H(10)=10: 冲突 → L[12]=10
H(79)=1: 冲突 → L[9]=79

最终散列表:

索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
值: [ ] [14] [1] [68] [27] [55] [19] [20] [84] [79] [23] [11] [10] [ ] [ ] [ ]

查找84:

H(84)=6: L[6]=19≠84
H₁=(6+1)=7: L[7]=20≠84
H₂=(6+2)=8: L[8]=84 查找成功,比较3次


各算法ASL总结表

算法 数据结构 成功ASL 失败ASL 时间复杂度
顺序查找 无序表 (n+1)/2 n+1 O(n)
折半查找 有序顺序表 ≈log₂(n+1)-1 ≈log₂(n+1) O(log₂n)
分块查找 分块表 √n+1 --- O(√n)
BST(平衡) 二叉树 O(log₂n) O(log₂n) O(log₂n)
AVL 二叉树 O(log₂n) O(log₂n) O(log₂n)
散列表(拉链) 散列表 1+α/2 α+e⁻ᵅ O(1)~O(n)
散列表(线性探测) 散列表 (1+1/(1-α))/2 --- O(1)~O(n)

α为装填因子,通常取0.5~0.85


七、思考

  1. 折半查找 ≈ 猜数字游戏

每次猜中间的数,根据提示往左或往右缩小区间。这就是折半(二分)查找。
2. 二叉排序树 ≈ 查字典

从中间翻开,比当前页小就往左翻,大就往右翻。BST就是这种"二分思维"的树形表达。
3. 平衡二叉树 ≈ 整理书架

书架歪了就要调整,让左右两边高度差不多,这样才能保持查找效率。
4. 红黑树 ≈ 地铁线路图

不是每条线路都完全对称(不像AVL那么严格),但整体平衡,保证从起点到任何站点的路径不会太长。
5. B树 ≈ 图书馆索书系统

图书馆有很多书架(分支),每个书架有多层(关键字),根据索书号快速定位到书架和层,而不是一本一本找。
6. 散列表 ≈ 超市存包柜

你的取件码(Hash值)直接告诉你柜子编号(地址),直接去开柜,不需要翻找。

注:以上内容参考 2027年数据结构考研复习指导 王道论坛 组编,其中有一些个人想法,如有任何错误或不妥,欢迎各位大佬指出,如果各位有一些有意思的想法,也可以和我交流一下~感谢!

八、明日计划

排序算法(上)

相关推荐
我星期八休息1 小时前
Linux 进程核心原理全解:从冯诺依曼体系到进程控制全链路深度剖析
大数据·linux·服务器·开发语言·数据结构·c++·散列表
xin_nai2 小时前
LeetCode热题100(Java)(3)滑动窗口
算法·leetcode·滑动窗口
自我意识的多元宇宙2 小时前
【数据结构】图-----关键路径
数据结构
黎阳之光2 小时前
视频孪生赋能智慧能源园区:黎阳之光打造全域数智化新标杆
大数据·人工智能·算法·安全·数字孪生
yyy(十一月限定版)2 小时前
ACM&OJ最终选拔赛
算法
啦啦啦_99992 小时前
1. 线性回归之 导数偏导数
算法·矩阵·线性回归
如君愿2 小时前
考研复习 Day 23 | 习题--计算机网络第一章、数据结构线性表
数据结构·计算机网络·考研·课后习题·记录考研
itzixiao2 小时前
L1-058 6翻了(15分)[java][python]
java·开发语言·python·算法
念何架构之路2 小时前
数组和切片实战
数据结构·算法·排序算法