散列查找及性能分析
这一节是第 7 章计算题的终点站。 教材的措辞是:「读者应能在给出散列表的长度、元素个数及散列函数和解决冲突的方法后,在求出散列表的基础上计算出查找成功时的平均查找长度和查找不成功的平均查找长度。」
难点只有一个:两个
机制
查找过程
散列表的查找过程与构造散列表的过程基本一致。对于一个给定的关键字 key,根据散列函数可以计算出其散列地址,执行步骤如下:
初始化:Addr = Hash(key);
- 检测查找表中地址为
Addr的位置上是否有记录:- 若无记录,返回查找失败;
- 若有记录,比较它与
key的值,若相等则返回查找成功标志,否则执行步骤 ②。
- 用给定的处理冲突方法计算「下一个散列地址」,并把
Addr置为此地址,转入步骤 ①。
边界辨析:
「位置为空」才是失败的判据,不是「探遍全表」。 只要撞上一个空位就立刻返回失败—— 因为若该关键字存在,它在插入时就会占住这个空位。 这也正是 7.5.3 里「开放定址法不能物理删除」的原因: 物理删除会凭空造出一个空位,把后面同义词的查找路径截断。
完整例子:线性探测
关键字序列
| 地址 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 关键字 | 14 | 01 | 68 | 27 | 55 | 19 | 20 | 84 | 79 | 23 | 11 | 10 |
查找给定值 84:首先求得散列地址 L[6] 不空且 L[6]≠84,则找第一次冲突处理后的地址 L[7] 不空且 L[7]≠84,则找第二次冲突处理后的地址 L[8]=84,查找成功,返回记录在表中的序号 8。比较 3 次。
查找给定值 38:先求散列地址 L[12] 不空且 L[12]≠38,则找下一地址 L[13] 是空记录,所以表中不存在关键字为 38 的记录。比较 2 次。
各关键字的比较次数(教材图 7.35):
| 关键字 | 14 | 01 | 68 | 27 | 55 | 19 | 20 | 84 | 79 | 23 | 11 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 比较次数 | 1 | 2 | 1 | 4 | 3 | 1 | 1 | 3 | 9 | 1 | 1 | 3 |
79 那个 9 值得看一眼:
对同一组关键字,设定相同的散列函数,则不同的处理冲突的方法得到的散列表不同,它们的平均查找长度也不同。
不成功的 :分母是值域大小
理由从 7.5.2 的第 1 条要求直接来:散列函数的值域范围依赖于散列表的大小,而一个待查关键字的起点只可能落在值域内的某个地址上。地址 13、14、15 永远不会是任何
对上例(
| 起点 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 比较次数 | 1 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
注意起点 1 走了 13 次:从地址 1 一路撞到地址 12 全非空,直到地址 13 才空——这一个数字就把「线性探测的堆积有多贵」说清楚了。
关联对照:
装填因子
散列表的平均查找长度依赖于散列表的装填因子
散列表的查找效率取决于三个因素:散列函数、处理冲突的方法和装填因子。
边界辨析:
「不直接依赖于
或 」是这一条的重点。 一张 的表和一张 的表, 都是 0.5,平均查找长度是同一个量级——表变大十倍,查找并不变慢。 这正是散列表被说成 的根据:代价只与「满的程度」有关,与「规模」无关。 与之对照,折半查找 的 是 ,直接依赖 。
教材 7.5.5 的题目里给过线性探测的一个近似式(作为题干条件给出,不是要求推导的结论):
手算模板
第一步:把表填出来(7.5.3 的模板),边填边记每个关键字的比较次数。
第二步:
第三步:
- 起点范围是
(散列函数的值域),不是 。 - 对每个起点,沿冲突处理方法走,数到第一个空位为止,空位算一次。
- 除以
。
拉链法的两个
| 数什么 | 分母 | |
|---|---|---|
| 成功 | 每个结点在其链中的序号 | 记录数 |
| 不成功 | 每条链的长度(走到链尾的空指针;空链算 0 或 1 按题目约定) | 值域大小 |
三个分母,一个都不能混:成功除
边界
| 说法 | 判断 | 说明 |
|---|---|---|
| 「 | ❌ | 是散列函数的值域大小 |
| 「 | ❌ | 同上; |
| 「查找失败要探遍全表」 | ❌ | 撞到第一个空位就失败 |
| 「空位不计入比较次数」 | ❌ | 计入——「检测该位置是否有记录」本身就是一次访问 |
| 「装填因子 | ❌ | |
| 「 | ❌ | 依赖于 |
| 「 | ❌ | 冲突与 |
| 「散列表查找成功的 ASL 仅与表长有关」 | ❌ | 取决于散列函数、处理冲突的方法和装填因子三者 |
| 「同一组关键字、同一散列函数,ASL 唯一」 | ❌ | 不同的冲突处理方法得到不同的表和不同的 ASL |
| 「散列查找不需要比较关键字」 | ❌ | 定位后必须比较,所以仍以 ASL 度量效率 |
口径差异:
算竞里评价哈希表只看「均摊
」和「会不会被卡」,从不算 。 408 恰恰只考 ,而且两个 的分母是全章最容易错的地方—— 三个不同的分母( 、 、 )在同一道题里同时登场。 这一节没有任何算法思想,纯粹是「按定义数数」,失分全在读题不细。
对照速查
| 量 | 式子 | 分母 |
|---|---|---|
| 记录数 | ||
| 值域大小 | ||
| 装填因子 | 表长 | |
| 线性探测近似 | — |
| 教材例题( | 值 |
|---|---|
| 2.5 | |
| 7 | |
| 方法 | 成功数什么 | 不成功数什么 |
|---|---|---|
| 开放定址 | 从 | 从起点到第一个空位的次数 |
| 拉链法 | 结点在链中的序号 | 链的长度 |
考点
- 两个
的分母—— 与 ,全章最高频失分点。 - 查找失败的判据是「遇到空位」,且空位计一次比较。
- 装填因子
,且 只依赖 (2011、2022 命题追踪)。 - 影响查找效率的三个因素。
- 同一组关键字、不同冲突处理方法 → 不同的表和不同的 ASL。
- 完整填表 + 算两个 ASL(2010、2018、2019、2024 命题追踪,几乎年年考)。
链接
- 🏠 返回总览:数据结构第 7 章:查找总览
- ⬅️ 上一节:7.5.3 处理冲突的方法
- 🔗 表的构造:7.5.3 处理冲突的方法
- 🔗
与 的区分:7.5.1 散列表的基本概念 - 🔗 ASL 的定义:7.1 查找的基本概念
- 🔗 直接依赖
的对照:7.2.2 折半查找 - 📖 名词库:第 7 章名词库