Appearance
分块查找(索引顺序查找)
2026 大纲 六(三)分块查找法。
退一步换灵活性
折半查找要求全表有序,代价是插一个元素平均要移动一半元素,动态场景下用不了。顺序查找对结构毫无要求,代价是
分块查找卡在两者中间,做法是放弃"全表有序"这个强条件,只保留一半:
🔴 块间有序、块内无序:第
块的最大关键字 < 第 块的最小关键字;块内元素可以任意排列。
这一刀切得很划算,因为它把两个好处各留了一半:
- 块间有序让你能跳过整块——这是分块比顺序查找快的全部来源。
- 块内无序让插入删除不必移动元素——找到该去哪一块,往块内的空位一放就行。
教材把它总结成一句话:如果线性表既要快速查找又经常动态变化,则可采用分块查找。
配套的结构是一张索引表,每项是 (该块的最大关键字, 块的起始地址):
索引表: [max₁, addr₁] [max₂, addr₂] [max₃, addr₃]
↓ ↓ ↓
数据表: [ 块1: 无序 ] [ 块2: 无序 ] [ 块3: 无序 ]
约束: 块1 全部元素 ≤ max₁ < 块2 全部元素 ≤ max₂ < 块3 全部元素 ≤ max₃为什么索引项存的是最大值而不是最小值? 因为查找条件是"找第一个
还有一个推论:索引表本身按
先动手看一眼
两阶段与代码
查找分两步:① 在索引表里定位块(顺序、折半都行);② 块内查找。
⚠️ 第二步只能用顺序查找——块内是无序的,折半的前提不成立。这条约束在算 ASL 时会一直跟着你。
c
typedef struct {
int maxKey; // 该块内的最大关键字
int addr; // 该块在数据表中的起始下标
} IndexItem;
// 索引表用折半查找,块内用顺序查找;返回下标,-1 表示失败
int blockSearch(int A[], int n, IndexItem idx[], int b, int key) {
int lo = 0, hi = b - 1, blockIdx = -1;
while (lo <= hi) { // 找第一个 maxKey >= key 的索引项
int mid = lo + (hi - lo) / 2;
if (idx[mid].maxKey >= key) {
blockIdx = mid; // 记下候选,继续往左找更小的下标
hi = mid - 1; // "找第一个满足条件的位置"的标准写法
} else {
lo = mid + 1;
}
}
if (blockIdx == -1) return -1; // 所有块的最大值都比 key 小
int start = idx[blockIdx].addr;
int end = (blockIdx + 1 < b) ? idx[blockIdx + 1].addr : n; // 下一块起点即本块终点
for (int i = start; i < end; i++)
if (A[i] == key) return i;
return -1; // 块内没有就一定不在表中——块间有序保证了这一点
}⚠️ 最后那行 return -1 是重点:块内扫完就判失败,不去看下一块。这是"块间有序"这条约束的直接回报,也是分块查找相对纯顺序查找的全部性能来源。
ASL:两段串联,优化就是让两段平衡
索引与块内都用顺序查找时(
现在问:
🔴
时 ASL 最小,最小值是 。
推导(均值不等式):对正数
等号成立当且仅当
极值点的含义值得单说:
⚠️ 别丢那个
。 两个 相加正好是 1,最优值是 而不是 。不过要注意问法:题目问"每块应包含多少元素"时答的是 ;问"最小 ASL 是多少"时才是 。
数值验证(
| 块长 | 块数 | |
|---|---|---|
| 1 | 16 | |
| 2 | 8 | |
| 4 | 4 | |
| 8 | 2 | |
| 16 | 1 |
一次完整的两阶段演算(第一次学、或想手动模拟时展开)
18 个记录分成 3 块,每块 6 个(
索引表: [22, 起始 1] [48, 起始 7] [86, 起始 13]
数据表: 下标 1~6: 22 12 13 8 9 20 ← 块内乱序,但全部 ≤ 22
下标 7~12: 33 42 44 38 24 48 ← 全部在 (22, 48]
下标 13~18: 60 58 74 49 86 53 ← 全部在 (48, 86]先验证块间有序:块 1 的最大值 22 < 块 2 的最小值 24 ✓;块 2 的最大值 48 < 块 3 的最小值 49 ✓。块内怎么乱都不影响这条约束。
查找 38(成功)
- 查索引表:
(第 1 次比较)→ (第 2 次比较)→ 确定在块 2; - 块内从下标 7 顺序扫:33(1 次)→ 42(2 次)→ 44(3 次)→ 38(4 次,命中)。
合计
查找 29(失败)
- 查索引表:
、 → 块 2(2 次比较); - 块内从下标 7 扫到下标 12,6 个元素全部比完仍未命中(6 次比较);
- 到此即可判失败——不需要再去看块 1 和块 3。
合计
索引表改用折半查找:精确式、教材近似式与两种算法(题目让索引表折半时展开)
此时
教材把末尾这个
这就是严蔚敏教材给出的形式——它是在"
⚠️ 两种算法要分清:
| 写法 | 它是什么 | 什么时候用 |
|---|---|---|
| 折半查找的平均比较次数(近似式) | 求平均 ASL,且题目按近似式给答案 | |
| 折半判定树的高度,即最坏比较次数 | 求"最多比较几次";用它算出的是上界不是平均值 |
题目若给出具体的
最优块长仍在
失败 ASL 的推法与一次逐入口累加(题目要求算失败 ASL 时展开)
教材只给了成功 ASL。框架是固定的:
一次失败查找的比较次数 = 索引表上定位块所用的比较次数 + 扫完整个块的
次比较。 特殊情形: 大于全表最大值时,索引表全部比完就判失败,不进入任何块。
| 失败入口 | 索引比较 | 块内比较 | 合计 |
|---|---|---|---|
| 1 | 4 | 5 | |
| 2 | 4 | 6 | |
| 3 | 4 | 7 | |
| 4 | 4 | 8 | |
| 4 | 0(无块可查) | 4 |
这个数不必记,要记的是推法:先数清有几个互不等价的失败入口,再对每个入口把两段比较次数加起来。这正是《查找基本概念》里那条通用规则的应用。
三种方法的复杂度对照,以及分块的插入删除代价
| 查找方式 | 时间复杂度 | 最优 ASL | 前提 |
|---|---|---|---|
| 顺序查找 | 无 | ||
| 折半查找 | 有序 + 顺序存储 | ||
| 分块查找(顺序索引) | 块间有序 | ||
| 分块查找(折半索引) | 块间有序 + 索引表有序 |
空间复杂度
插入删除:只需
考点速记
三条结论:
- 块间有序、块内无序;块内只能顺序查找。
,两段串联;优化的本质是让两段工作量相等。 - 顺序索引时
最优, 。
这一节在真题里被考过的形式(下方「真题练习」逐题对应):
- 求最优块长:给出表长(如 400)、说明"均匀分块""查找概率相同"、并指明索引表和块内都用顺序查找,问每块包含多少元素效率最高。直接答
——400 个元素每块 20 个。⚠️ 看清问的是块长(答 )还是最小 ASL(答 ),两个数差 1,选项里常常都有。
易错:块内不能折半查找。 块内是无序的,看到"块内也采用折半"要先怀疑题面,或按题目特别说明的"块内也有序"处理。
易错:
是块长不是块数(虽然最优时两者相等,都等于 )。题目若不是均匀分块,或索引用折半,这个等号就不成立了。
易错:
,那个 别丢。 两个 各贡献 。
教材出处
- 严蔚敏《数据结构(C 语言版)》(第 2 版)7.2.3 节「分块查找」,p197: 分块查找又称索引顺序查找,"性能介于顺序查找和折半查找之间"; 索引项包含"关键字项(其值为该子表内的最大关键字)和指针项(指示该子表的第一个记录在表中位置)"; "分块有序"的定义;以及"由于由索引项组成的索引表按关键字有序,则确定块的查找可以用顺序查找, 亦可用折半查找,而块中记录是任意排列的,则在块中只能是顺序查找"。 同页给出
与顺序索引情形的展开式,并指出 "当 取 时, 取最小值 "。 - 同书 p198:折半索引情形的近似式
; 以及分块查找的优缺点——"在表中插入和删除数据元素时,只要找到该元素对应的块, 就可以在该块内进行插入和删除运算。由于块内是无序的,故插入和删除比较容易, 无需进行大量移动……其缺点是:要增加一个索引表的存储空间并对初始索引表进行排序运算"。
相关知识
顺序查找(块内查找就是它)|折半查找(索引表可用它)| 查找基本概念(数失败入口的方法是本篇失败 ASL 的依据)| B 树、B+ 树(把一级索引推广成多级、索引组织成平衡树;B+ 树的叶结点链是"块间有序"在外存上的极致形式)| 查找算法分析与对比(分块正好卡在顺序与折半之间)