@散列表
hashtable,关键问题在于查找的时间优化和解决冲突。
1. 直接寻址表
index和key值直接相关,存储全域U
2. 散列表
即利用散列函数h由关键字k计算出槽的位置,采用函数h将全域U映射到散列表的槽位上。散列,顾名思义,希望h尽可能随机混杂,最大程度减少冲突。但由于h(k)的种数m<U, 所以一定会有冲突的情况产生。
2.1 解决冲突的方法
2.1.1 链接法
在链接法中,把散列到同一槽的所有元素都放在一个链表中。槽j中有一个指针,指向存储所有到j的元素的表头。其性能主要取决于散列函数。好的散列函数应满足简单均匀散列假设,即每个关键字都被等可能的散列到m个槽位中的任何一个。
2.2 散列法
2.2.1 除法散列法
h
(
k
)
=
k
m
o
d
m
,
h(k)=k\ mod\ m,
h(k)=k mod m,
其中
m
m
m应取一些不太接近2的整数幂的素数
2.2.2 乘法散列法
h
(
k
)
=
f
l
o
o
r
(
m
(
k
A
m
o
d
1
)
)
,
h(k) = floor(m(kA\ mod\ 1)),
h(k)=floor(m(kA mod 1)),
其中
f
l
o
o
r
(
⋅
)
floor(\cdot)
floor(⋅)表示向下取整,先用k乘上常数A(0<A<1),提取小数部分,再乘上m,向下取整。m一般取2的幂次,比较好实现。而A有文献表明取
(
5
−
1
)
/
2
(\sqrt{5}-1)/2
(5−1)/2是个比较好的值。
2.2.3 全域散列法
某些情况下,对于一个特定的散列函数,还是会出现关键字全部散列到一个槽中。为改善这一情况,可采用随机选择散列函数,使函数的性质和选择的关键字保持较好的独立性,这称为全域散列。设计一个全域散列函数类比较容易,常见的有
h
a
b
(
k
)
=
(
(
a
k
+
b
)
m
o
d
p
)
m
o
d
m
h_{ab}(k) = ((ak+b)\ mod\ p)\ mod\ m
hab(k)=((ak+b) mod p) mod m
其中a有p-1种选择,b有p种选择。
2.3 开放寻址法
在开放寻址法种,所有的元素都存放在散列表里。为使用开放寻址法插入一个元素,需要连续的检查散列表,找到适合的空位,这称为探查,其依赖于待插入的关键字。此时散列函数改为
h
:
U
×
{
0
,
1
,
…
,
m
−
1
}
↦
{
0
,
1
,
…
,
m
−
1
}
.
h: U\times \{ 0,1,\ldots,m-1 \} \mapsto \{0,1,\ldots,m-1\}.
h:U×{0,1,…,m−1}↦{0,1,…,m−1}.
如果位置是空的,就用,否则将会继续向前找。注意这种方法可能会出现散列表满的情况。
找的时候也类似。但注意到这种情况,删除不方便,所以要考虑删除的话,还是会用链接法来解决冲突。
下面介绍一些探查法,以接近均匀散列的假设。但只是接近而已,双重散列的效果似乎是最好的。
2.3.1 线性探查
先假设一个普通的散列函数h’, 称之为辅助散列函数。线性探查采用的散列函数为:
h
(
k
,
i
)
=
(
h
′
(
k
,
i
)
+
i
)
m
o
d
m
,
i
=
0
,
1
,
…
,
m
−
1.
h(k,i) = (h'(k,i)+i)\ mod\ m, i=0,1,\ldots, m-1.
h(k,i)=(h′(k,i)+i) mod m,i=0,1,…,m−1.
给定一个关键字k,首先探查槽T[h’(k)],然后是T[h’(k)+1],…,一直到T[m-1]。随后返回T[0], T[1],…,一直到T[h’(k)-1]。有m种不同的探查序列。这个方法比较容易,但易出现一次群集的问题,即连续占用的槽越来越长,带来性能下降。
2.3.2 二次探查
h
(
k
,
i
)
=
(
h
′
(
k
)
+
c
1
i
+
c
2
i
2
)
m
o
d
m
,
i
=
0
,
1
,
…
,
m
−
1.
h(k,i) = (h'(k)+c_1i+c_2i^2)\ mod\ m, i=0,1,\ldots, m-1.
h(k,i)=(h′(k)+c1i+c2i2) mod m,i=0,1,…,m−1.
群集现象轻,二次群集。为充分利用散列表,
c
1
,
c
2
,
m
c_1,c_2,m
c1,c2,m值有限制。
2.3.3 双重散列
双重散列是用于开放寻址法的最好方法之一。其散列函数形式为
h
(
k
,
i
)
=
(
h
1
(
k
)
+
i
h
2
(
k
)
)
m
o
d
m
,
i
=
0
,
1
,
…
,
m
−
1.
h(k,i) = (h_1(k)+ih_2(k))\ mod\ m, i=0,1,\ldots, m-1.
h(k,i)=(h1(k)+ih2(k)) mod m,i=0,1,…,m−1.
这里
h
2
(
k
)
h_2(k)
h2(k)必须要与表的大小m互素,取m为2的幂,并设计一个总产生奇数的
h
2
h_2
h2就可以。
2.4 完全散列
使用完全散列进行查找时,能在最坏情况下用O(1)次访存完成。例如可设计两级散列方案,每级上都使用全域散列。为确保二次散列表不冲突,需此二次散列表 S j S_j Sj的大小 m j m_j mj为散列在槽 j j j中的关键字 n j n_j nj的平方。通过适当选择第一级散列函数,可以使总体存储空间限制为 O ( n ) O(n) O(n).