文章目录
Definition
散列(哈希)函数
通过一个**散列函数(哈希函数)**将关键字映射到对应的关键字的地址的函数记为:
H
a
s
h
(
k
e
y
)
=
A
d
d
r
Hash(key)=Addr
Hash(key)=Addr
其中,地址
A
d
d
r
Addr
Addr 既可以是数组下标也可以是索引或者内存地址
冲突
因为映射的值域是有限的,通常远小于定义域,也就是说,散列表可能会把两个或两个以上的关键字映射到同一个地址上,这种情况称为冲突,发生碰撞的不同关键字称为同义词
散列(哈希)表
根据关键字进行直接访问的数据结构,建立了关键字和存储地址之间的一种直接映射关系
构造散列函数
直接定址法
取关键字的线性函数值:
H
(
k
e
y
)
=
a
×
k
e
y
+
b
H(key)=a\times key+b
H(key)=a×key+b
其中,
a
,
b
a,b
a,b 都是常数。这种方法非常简单,并且是一个一一映射,不会产生冲突,适合关键字的分布基本连续的情况,否则会产生大量空位造成空间浪费
除留余数法
最简单,最常用的方法,取一个不大于列表长度但是最接近的一个质数
p
p
p,利用以下公式把关键字转换成散列地址:
H
(
k
e
y
)
=
k
e
y
%
p
H(key)=key\%p
H(key)=key%p
选好
p
p
p 可以减少冲突,使得每个关键字通过函数转换之后等概率的分布在散列空间上
数字分析法
根据关键字某一位数的数码作为散列地址,例如十进制数,可以选取个位数作为散列地址,散列空间为 [ 0 , 9 ] [0,9] [0,9]。需要注意的是,每个位置上的数码的分布不一定是均匀的,比如在某个位置上可能只有几个数码出现的频率较大,而在一些位置上每个数码分布都比较均匀,这时我们需要选取数码分布较为均匀的若干位作为散列地址,减少冲突。
平方取中法
将关键字取平方之后选取中间若干位作为散列地址,具体情况具体分析。得到的散列地址和关键字的每一位都有关系,因此散列地址分布比较均匀,适合关键字每个位取值都不太均匀的情况,或者均小于散列地址所需的位数。
折叠法
将关键字分割成位数相同的几个部分,取叠加和作为散列地址,适用于关键字位数很多并且数字分布均匀的情况
冲突处理
开放定址法
空闲地址可以向非同义词开放,也就是说,当发生冲突的时候,可以查看其他空闲的地址,存放这个与原来地址冲突的关键字。递推公式:
H
i
=
(
H
(
k
e
y
)
+
d
i
)
%
m
H_{i}=(H(key)+d_{i})\%m
Hi=(H(key)+di)%m
其中,
i
=
0
,
1
,
2
⋯
,
k
(
k
≤
m
−
1
)
i=0,1,2\cdots,k(k\le m-1)
i=0,1,2⋯,k(k≤m−1),
m
m
m 为散列表表长;
d
i
d_{i}
di 为增量序列。取定增量之后,处理方法是确定的,通常有:
- 线性探测法:取一个线性增量,即 d i = 0 , 1 , 2 , ⋯ , m − 1 d_{i}=0,1,2,\cdots,m-1 di=0,1,2,⋯,m−1,顺序查看表中的下一个单元(当探测到表尾地址 m − 1 m-1 m−1 时,下一个地址是 0 0 0 )直到找到一个空闲的单元,否则遍查全表。这种做法使得本来应该存放在地址 i i i 上的元素存放到地址 i + 1 i+1 i+1 上,以此类推,后续元素不断竞争位置,容易造成元素堆积在相邻的地址,降低查找效率(即同义词顺序存储,查找时变为顺序查找,效率极差)
- 平方探测法:即 d i = 0 2 , 1 2 , − 1 2 , 2 2 . − 22 , ⋯ , k 2 , − k 2 d_{i}=0^{2},1^{2},-1^{2},2^{2}.-2{2},\cdots,k^{2},-k^{2} di=02,12,−12,22.−22,⋯,k2,−k2,其中 k ≤ m / 2 k\le m/2 k≤m/2,散列表长度必须时一个可以表示成 4 k + 3 4k+3 4k+3 的一个素数,可以避免堆积,但是无法探测所有单元,可以探测至少一半
- 再散列探测法:再使用一个哈希函数进行映射,即
d
i
=
H
a
s
h
2
(
k
e
y
)
d_{i}=Hash_{2}(key)
di=Hash2(key),又称为双散列法,当通过第一个散列函数发生冲突时,使用第二个哈希函数计算地址增量:
H i = ( H ( k e y ) + i × H 2 ( k e y ) ) % m H_{i}=(H(key)+i\times H_{2}(key))\%m Hi=(H(key)+i×H2(key))%m
其中, i i i 是冲突次数,初始为 0 0 0。最多经过 m − 1 m-1 m−1 次探测就可以遍历全部。
拉链法 Chaining
很简单,当发生冲突的时候,把同义词存放在一个线性链表里面,散列地址唯一标识这个线性链表的表头,适用于经常插入删除的情况
About Performance
查找:
A
d
d
r
=
H
a
s
h
(
k
e
y
)
Addr=Hash(key)
Addr=Hash(key)
一种是:直接映射后判断是否有记录,无记录返回失败,有则比较关键字的值,相等返回成功;另一种是通过给定的处理冲突的方法计算下一个散列地址,查找记录。例如散列函数
H
(
k
e
y
)
=
k
e
y
%
13
H(key)=key\%13
H(key)=key%13,采用线性探测法增量为
1
1
1 的散列表:
哈希地址 | 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:
- 计算哈希值 H ( 84 ) = 6 H(84)=6 H(84)=6, L ( 6 ) L(6) L(6) 不空并且关键字不相等
- 计算下一个哈希地址值 H 1 = ( 6 + 1 ) % 16 = 7 H_{1}=(6+1)\%16=7 H1=(6+1)%16=7 (不是13,是表长16),不空且不相等
- 计算下一个哈希地址值 H 2 = ( 7 + 1 ) % 16 = 8 H_{2}=(7+1)\%16=8 H2=(7+1)%16=8,不空且相等,返回记录序号为 8 8 8
散列表的查找效率取决于三个因素:
- 散列函数
- 冲突处理方法
- 装填因子
装填因子
一般记为
α
\alpha
α ,定义散列表的装满程度,即:
α
=
n
m
\alpha=\frac{n}{m}
α=mn
其中,
n
n
n 为表中记录的数量,
m
m
m 为散列表长度。散列表的装满程度直接以来于
α
\alpha
α 而不直接依赖于
n
,
m
n,m
n,m。可以看出
α
\alpha
α 越大,记录越满,冲突可能性越大。
不同冲突处理方法对应的查找时间复杂度如下:
冲突处理方法 | 时间复杂度 | |
---|---|---|
查找成功 | 查找失败 | |
线性探测法 | S≈1/2(1+1/(1-α)) | U≈1/2(1+1/(1-α)^2) |
二次探测法和双哈希法 | S≈-1/α+ln(1-α) | U≈1/(1-α) |
拉链法 | S≈1+α/2 | U≈α+e^{-α} |