408答疑
文章目录
五、散列(Hash)表
散列表的基本概念
核心定义
- 散列表(哈希表):通过哈希函数直接对关键字进行映射访问的表。
- 散列函数(哈希函数):将关键字映射到存储地址的函数,记为 H a s h M a p ( k e y ) = A d d r HashMap(key)=Addr HashMap(key)=Addr(地址可为数组下标、内存地址等)。
- 冲突:不同关键字被映射到同一地址的现象(如 k e y 1 ≠ k e y 2 key_1 \neq key_2 key1=key2 但 H a s h M a p ( k e y 1 ) = H a s h M a p ( k e y 2 ) HashMap(key_1)=HashMap(key_2) HashMap(key1)=HashMap(key2))。
- 理想时间复杂度:查找操作的时间复杂度为 O ( 1 ) O(1) O(1)(与数据量无关)。
查找及性能分析
平均查找长度(ASL)
- 定义:衡量散列表查找效率的核心指标,需分别计算搜索成功和失败的情况。
- 冲突的影响:即使通过散列函数建立了直接映射,冲突仍导致查找过程需进行关键字比较。
搜索成功的平均搜索长度
- 定义:找到表中已有元素所需的平均探查次数。
- 计算方式:所有已有元素探查次数的平均值(例如:若表中元素探查次数分别为 k 1 , k 2 , … , k n k_1, k_2, \dots, k_n k1,k2,…,kn,则 A S L 成功 = 1 n ∑ i = 1 n k i ASL_{成功} = \frac{1}{n}\sum_{i=1}^n k_i ASL成功=n1∑i=1nki)。
搜索失败的平均搜索长度
- 定义:在表中未找到目标元素时,定位到插入位置所需的平均探查次数。
- 计算方式:所有可能散列地址上插入新元素时探查空桶次数的平均值(需覆盖散列函数所有可能结果)。
查找效率影响因素
- 散列函数设计:应尽量减少冲突(如均匀分布关键字)。
- 冲突处理方法:包括开放定址法、链地址法等。
- 装载因子:定义为 α = 表中元素数 散列表长度 \alpha = \frac{\text{表中元素数}}{\text{散列表长度}} α=散列表长度表中元素数, α \alpha α 越大冲突概率越高。
散列表特性
- 关键字与存储地址的直接映射关系区别于线性表、树表(需逐次比较)。
- 实际应用中需权衡 α \alpha α 值以平衡空间和时间效率。
散列函数的构造方法
-
在构造散列函数时,必须注意以下几点:
-
定义域和值域:散列函数的定义域必须包含全部关键字,而值域的范围则依赖于散列表的大小。
-
均匀分布:散列函数计算出的地址应尽可能均匀地分布在整个地址空间,尽可能地减少冲突。
-
计算效率:散列函数应尽量简单,能在较短的时间内计算出任意一个关键字对应的散列地址。
-
-
散列函数的目标:采用何种构造散列函数的方法取决于关键字集合的情况,但最终的目标都是尽量降低产生冲突的可能性。
直接定址法
- 核心思想:直接取关键字的某个线性函数值为散列地址。
- 散列函数:
H ( k e y ) = k e y H(key) = key H(key)=key 或 H ( k e y ) = a × k e y + b H(key) = a \times key + b H(key)=a×key+b( a a a 和 b b b 为常数) - 特点:
- 计算简单且不会产生冲突。
- 若关键字分布不连续,会导致存储空间浪费。
- 适用场景:关键字分布基本连续的情况(如顺序编号的身份证号、学号)。
除留余数法
- 核心思想:通过取模运算将关键字映射到散列地址。
- 散列函数:
H ( k e y ) = k e y m o d p H(key) = key \mod p H(key)=keymodp ( p p p 为不大于散列表长度 m m m 且最接近 m m m 的质数) - 特点:
- 最简单、最常用的冲突控制方法。
- p p p 的选择直接影响冲突概率(需使映射结果均匀分布)。
- 适用场景:表长可动态调整的开放定址法场景。
数字分析法
- 核心思想:分析关键字各位分布规律,选取分布均匀的位作为散列地址。
- 操作步骤:
- 将关键字转换为 r r r 进制数(如十六进制)。
- 统计各数位上不同数码出现的频率。
- 选择分布最均匀的若干位组合成散列地址。
- 特点:高度依赖具体关键字集合,更换关键字需重新设计函数。
- 适用场景:已知关键字集合且分布规律可预先分析的场景(如固定格式的电话号码)。
平方取中法
- 核心思想:取关键字平方值的中间几位作为散列地址。
- 操作步骤:
- 计算关键字的平方值(如 k e y 2 key^2 key2)。
- 根据散列表长度截取平方值的中间若干位(如取中间 4 位)。
- 特点:
- 散列地址与关键字的每一位相关,分布更均匀。
- 需根据实际需求调整截取位数。
- 适用场景:关键字各位取值不均匀或位数不足(如短字符串、小范围整数)。
方法对比与选择原则
方法 | 优势 | 局限性 | 典型应用场景 |
---|---|---|---|
直接定址法 | 无冲突、计算快 | 空间利用率低 | 连续分布的关键字 |
除留余数法 | 灵活、适用性强 | 依赖质数选择 | 通用场景 |
数字分析法 | 针对性强、效率高 | 需预先分析关键字分布 | 固定格式数据 |
平方取中法 | 分布均匀、适应性强 | 计算复杂度略高 | 非均匀分布的关键字 |
处理冲突的方法
冲突的不可避免性
应该注意到,任何设计出来的散列函数都不可能绝对地避免冲突。为此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个“空”的Hash地址。用 H i H_i Hi 表示处理冲突中第 i i i 次探测得到的散列地址,假设得到的另一个散列地址 H 1 H_1 H1 仍然发生冲突,只得继续求下一个地址 H 2 H_2 H2。以此类推,直到 H k H_k Hk 不发生冲突为止,则 H k H_k Hk 为关键字在表中的地址。
闭散列(开放定址法)
- 闭散列,也称为开放定址法,是指表中可存放新表项的空闲地址既向它的同义词表项开放,又向它的非同义词表项开放。其数学递推公式为:
H i = ( H ( k e y ) + d i ) % m H_i = (H(key) + d_i) \% m Hi=(H(key)+di)%m
式中, H ( key ) H(\text{key}) H(key) 为散列函数; i = 1 , 2 , … , k i = 1, 2, \ldots, k i=1,2,…,k( k ≤ m − 1 k \leq m - 1 k≤m−1); m m m 表示散列表表长; d i d_i di 为增量序列。所有的数据都是在封闭空间内进行定位存储,不增加新的存储空间。 - 取定某一增量序列后,对应的处理方法就是确定的。通常有以下 4 种取法:
线性探测法
概述
线性探测法,又称线性探测再散列法。 d i = 1 , 2 , … , m − 1 d_i = 1, 2, \ldots, m - 1 di=1,2,…,m−1。它的特点是:冲突发生时,顺序查看表中下一个单元(探测到表尾地址 m − 1 m-1 m−1 时,下一个探测地址是表首地址 0),直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍全表。
特点
线性探测法可能使第 i i i 个散列地址的同义词存入第 i + 1 i+1 i+1 个散列地址,这样本应存入第 i + 1 i+1 i+1 个散列地址的元素就争夺第 i + 2 i+2 i+2 个散列地址的元素的地址……从而造成大量元素在相邻的散列地址上聚集(或堆积)起来,大大降低了查找效率。
示例分析
线性探测法是当hash一个关键字时,发现没有冲突,就保存关键字,如果出现冲突,就探测冲突地址下一个地址,依次按照线性查找,直到发现有空地址为止,从而解决冲突。
- 关键词:37, 25, 14, 36, 49, 68, 57, 11
- 散列表:HT[12],表的大小 m = 12 m = 12 m=12
- 散列函数: Hash ( x ) = x % 11 \text{Hash}(x) = x \% 11 Hash(x)=x%11
- 平均搜索长度(ASL)计算:
A S L s u c c = 1 8 ∑ i = 1 8 C i = 1 8 ( 1 + 1 + 3 + 4 + 3 + 1 + 7 + 1 ) = 21 8 ASL_{succ} = \frac{1}{8} \sum_{i=1}^{8} C_i = \frac{1}{8} (1 + 1 + 3 + 4 + 3 + 1 + 7 + 1) = \frac{21}{8} ASLsucc=81∑i=18Ci=81(1+1+3+4+3+1+7+1)=821
A S L u n s u c c = 2 + 1 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 1 11 = 40 11 ASL_{unsucc} = \frac{2 + 1 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + 1}{11} = \frac{40}{11} ASLunsucc=112+1+8+7+6+5+4+3+2+1+1=1140
平方探测法
概述
平方探测法,又称二次探测法。 d i = 1 2 , − 1 2 , 2 2 , − 2 2 , … , k 2 , − k 2 d_i = 1^2, -1^2, 2^2, -2^2, \ldots, k^2, -k^2 di=12,−12,22,−22,…,k2,−k2,其中 k ≤ m / 2 k \leq m/2 k≤m/2,散列表长度 m m m 必须是一个可以表示成 4 k + 3 4k + 3 4k+3 的素数。
特点
平方探测法是一种处理冲突的较好方法,可以避免出现“堆积”问题,它的缺点是不能探测到散列表上的所有单元,但至少能探测到一半单元。
示例分析
二次探测法指采用前后跳跃式探测的方法,发生冲突时,向后 1 2 1^2 12 位探测,向前 1 2 1^2 12 位探测,向后 2 2 2^2 22 位探测,向前 2 2 2^2 22 位探测……以跳跃式探测,避免堆积。
- 关键词:37, 25, 14, 36, 49, 68, 57, 11
- 散列表:HT[19],表的大小 m = 19 m = 19 m=19
- 散列函数: Hash ( x ) = x % 19 \text{Hash}(x) = x \% 19 Hash(x)=x%19
- 平均搜索长度(ASL)计算:
A S L s u c c = 1 8 ( 1 + 1 + 1 + 1 + 1 + 2 + 1 + 3 ) = 11 8 ASL_{succ} = \frac{1}{8} (1 + 1 + 1 + 1 + 1 + 2 + 1 + 3) = \frac{11}{8} ASLsucc=81(1+1+1+1+1+2+1+3)=811
A S L u n s u c c = 1 19 ( 2 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 1 + 1 + 3 + 4 + 2 + 1 + 2 + 1 + 1 + 3 + 4 ) = 33 19 ASL_{unsucc} = \frac{1}{19} (2 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 1 + 1 + 3 + 4 + 2 + 1 + 2 + 1 + 1 + 3 + 4) = \frac{33}{19} ASLunsucc=191(2+1+1+1+1+1+2+1+1+1+3+4+2+1+2+1+1+3+4)=1933
双散列法
概述
双散列法。
d
i
=
i
×
Hash
2
(
key
)
d_i = i \times \text{Hash}_2(\text{key})
di=i×Hash2(key)。需要使用两个散列函数,当通过第一个散列函数
H
(
key
)
H(\text{key})
H(key) 得到的地址发生冲突时,则利用第二个散列函数
Hash
2
(
key
)
\text{Hash}_2(\text{key})
Hash2(key) 计算该关键字的地址增量。它的具体散列函数形式如下:
H
i
=
(
H
(
k
e
y
)
+
i
×
H
a
s
h
2
(
k
e
y
)
)
%
m
H_i = (H(key) + i \times Hash_2(key)) \% m
Hi=(H(key)+i×Hash2(key))%m
初始探测位置
H
0
=
H
(
key
)
%
m
H_0 = H(\text{key}) \% m
H0=H(key)%m。
i
i
i 是冲突的次数,初始为 0。
示例分析
数据冲突时,解决冲突将使用第二个散列函数,因此得名双散列法。
- 关键词:22, 41, 53, 46, 30, 13, 01, 67
- 散列表:HT[11], m = 11 m = 11 m=11
- 散列函数: Hash ( x ) = ( 3 x ) % 11 \text{Hash}(x) = (3x) \% 11 Hash(x)=(3x)%11
- 再散列函数: ReHash ( x ) = ( 7 x ) % 10 + 1 \text{ReHash}(x) = (7x) \% 10 + 1 ReHash(x)=(7x)%10+1
伪随机序列法
概述
伪随机序列法。 d i = d_i = di= 伪随机数序列。
开放定址法对比
方法 | 核心机制 | 优点 | 缺点 |
---|---|---|---|
线性探测法 | 顺序线性探测 | 实现简单 | 同义词堆积严重 |
平方探测法 | 平方偏移量跳跃探测 | 减少堆积 | 探测范围受限(50%空间) |
双散列法 | 双散列函数协同定位 | 冲突概率低 | 函数设计复杂度高 |
伪随机序列法 | 伪随机数引导探测路径 | 分布均匀性佳 | 随机数质量依赖性强 |
注意事项
采用开放定址法时,不能随便物理删除表中已有元素,否则会截断其他同义词元素的查找路径。因此,要删除一个元素时,可以做一个删除标记,进行逻辑删除。但这样做的副作用是:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用。
拉链法(链接法,Chaining)
开散列,也称为拉链法,是一种处理散列冲突的方法。当不同的关键字通过散列函数映射到同一地址时,为了避免非同义词发生冲突,可以将所有的同义词存储在一个线性链表中,这个线性链表由其散列地址唯一标识。
特点
- 散列地址为 i i i 的同义词链表的头指针存放在散列表的第 i i i 个单元中。
- 查找、插入和删除操作主要在同义词链中进行。
- 拉链法适用于经常进行插入和删除的情况。
示例分析
拉链法又叫链地址法,拉链法就是把具有相同散列地址的关键字(同义词)值放在同一个单链表中,简单来说拉链法就是数组加链表的组合结构。
- 关键词:37, 25, 14, 36, 49, 68, 57, 11
- 散列表:HT[11],表的大小 m = 11 m = 11 m=11
- 散列函数: Hash ( x ) = x % 11 \text{Hash}(x) = x \% 11 Hash(x)=x%11
- 平均搜索长度(ASL)计算:
A S L s u c c = 1 8 ( 1 + 1 + 2 + 1 + 2 + 3 + 1 + 1 ) = 12 8 = 1.5 ASL_{succ} = \frac{1}{8}(1+1+2+1+2+3+1+1) = \frac{12}{8} = 1.5 ASLsucc=81(1+1+2+1+2+3+1+1)=812=1.5
A S L u n s u c c = 1 11 ( 2 + 1 + 3 + 4 + 2 + 2 + 1 + 1 + 1 + 1 + 1 ) = 19 11 ≈ 1.73 ASL_{unsucc} = \frac{1}{11}(2+1+3+4+2+2+1+1+1+1+1) = \frac{19}{11} \approx 1.73 ASLunsucc=111(2+1+3+4+2+2+1+1+1+1+1)=1119≈1.73
六、参考资料
鲍鱼科技课件
b站免费王道课后题讲解:
网课全程班: