一、基本概念
根据设定的哈希函数 H ( k e y ) H(key) H(key) 和处理冲突的方法将一组关键字映射到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为 哈希表,这一映像过程称为哈希造表 或散列,所得存储位置称哈希地址 或 散列地址。
二、哈希函数的构造方式
什么是好的哈希函数?
若对于关键字集合中的任何一个关键字,经哈希函数映射到地址集合中任何一个地址的概率是相等的,则称此类哈希函数为 均匀的哈希函数 。换句话说,就是使关键字经过哈希函数得到一个“随机的地址”,以便使一组关键字的哈希地址均匀分布在整个区间中,从而减少冲突。
常用的构造哈希函数的方法有:
1、直接定址法
取关键字或关键字的某个线性函数值为哈希地址。即:
H ( k e y ) = k e y 或 H ( k e y ) = a ∗ k e y + b H(key)=key \ 或 \ H(key)=a*key+b H(key)=key 或 H(key)=a∗key+b
其中 a a a 和 b b b为常数(这种哈希函数叫做自身函数)。
例如:有一个从1岁到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字减一常数1: H ( k e y ) = k e y + ( − 1 ) H(key)=key+(-1) H(key)=key+(−1)。结果如下图所示:
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/4d4439cfd67294a54a3fbf0e3d5fbbba.png)
2、数字分析法
假设关键字是以 r r r 为基的数(如:以10位基的十进制),并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。
例:假设有80记录,其关键字为8位十进制数。假设哈希表的表长为100,则可取两位十进制数组成哈希地址。取哪两位?原则是使得到的哈希地址尽量避免产生冲突。
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/075ab6f0a87e3ad9c40c74b216af8be5.png)
对关键字分析发现,第1、2位都是“8 1”,第3位只能取1、2、3或4,第8位只能取2、5或7,因此这4位都不可取。由于中间的4位可看成是近乎随机的,因此可取其中任意两位,或取其中两位与另外两位的叠加求和后舍去进位作为哈希地址。可通过下面的式子获取4、5两位数。
H ( k e y ) = k e y % 100000 / 1000 H(key)=key\%100000/1000 H(key)=key%100000/1000
3、平方取中法
取关键字平方后的中间几位为哈希地址。这是一种较常用的构造哈希函数的方法。通常在选定哈希函数时不一定能知道关键字的全部情况,取其中哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。
4、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可能不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。关键字位数很多,而且关键字中每一位上数字分布大致均匀时,可以采用折叠法得到哈希地址。
(1)移位叠加:移位叠加是将分割后的每一部分的最低位对齐,然后相加。
(2)间界叠加:间界叠加是从一端向另一端沿分割界来回折叠,然后对齐相加。
例:设某图书馆的馆藏图书不足10000种,现欲以国际标准图书编号为关键字构造哈希表,试用折叠法设计哈希函数。如有国际标准图书编号0-442-20586-4分别用(a)移位叠加法和(b)间界叠加法的哈希地址分别如下所示:
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/0ef5461f8d79cba8943c4c380b5ea3f3.png)
5、除留余数法
取关键字被某个不大于哈希表表长
m
m
m的数
p
p
p除后所得余数为哈希地址。即
H
(
k
e
y
)
=
k
e
y
M
O
D
p
,
p
≤
m
H(key)=key \quad MOD \quad p,\ p \leq m
H(key)=keyMODp, p≤m
这是一种最简单,也最常用的构造哈希函数的方法。它不仅可以对关键字直接取模(MOD),也可在折叠、平方取中法等运算后取模。
例:设有一组关键字如下:(19,14,23,01,68,20,84,27,55,11,10,79),试用除留取余法设计哈希函数。
可令 p p p 为13,即令 H ( k e y ) = k e y % 13 H(key)=key\%13 H(key)=key%13。
注:对 p p p 要加一个限制:即 p p p一定要为质数。不然会增加冲突的可能。
6、随机法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即 H ( k e y ) = r a n d o m ( k e y ) H(key)=random(key) H(key)=random(key),其中 r a n d o m random random 为随机函数。通常,当关键字长度不等时采用此法构造哈希函数较恰当。
实际工作中需视不用的情况采用不同的哈希函数。通常,考虑的因素有:
(1)计算哈希函数所需时间(包括硬件指令的因素);
(2)关键字的长度;
(3)哈希表的大小
(4)关键字的分布情况;
(5)记录的查找频率
三、处理冲突的方法
处理冲突是指对于一个待插入哈希表的数据元素,若按给定的哈希函数求得的哈希地址已被占用,则按一定规则求下一个哈希地址,如此反复,直至找到一个可用的地址以保存该元素。
通常用的处理冲突的方法有以下几种:
1、开放定址法:
H i = ( H ( k e y ) + d i ) M O D m ; i = 1 , 2 , . . . , k ( k ≤ m − 1 ) H_i =(H(key)+d_i) \ MOD \ m \ \ ;i=1, 2,...,k(k\leq m-1) Hi=(H(key)+di) MOD m ;i=1,2,...,k(k≤m−1)
其中: H ( k e y ) H(key) H(key) 为哈希函数; m m m为哈希表表长; d i d_i di为增量序列,可有下列3中取法;(1) d i = 1 , 2 , 3 , . . . , m − 1 d_i=1,2,3,...,m-1 di=1,2,3,...,m−1,称 线性探测再散列 ; (2) d i = 1 2 , − 1 2 , 2 2 , − 2 2 , 3 2 , . . . , ± k 2 , ( k ≤ m / 2 ) d_i=1^2,-1^2,2^2,-2^2,3^2,...,\pm k^2,(k\leq m/2) di=12,−12,22,−22,32,...,±k2,(k≤m/2) 称 二次探测再散列 ;(3) d i = d_i= di= 伪随机数序列,称伪随机探测再散列。
例:设有一组关键字如下:(67,84,18,26,34,28),哈希函数为 H ( k e y ) = k e y H(key)=key%7 H(key)=key, 用线性探测再散列法处理冲突,试画出哈希表存储结构示意图。
已知:哈希函数: H ( k e y ) = k e y % 7 H(key)=key\%7 H(key)=key%7
有冲突时: H i = ( H ( k e y ) + d i ) % 7 H_i=(H(key)+d_i)\%7 Hi=(H(key)+di)%7
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/4d74bd82ba0b6604854aff0bc4e0d793.gif)
2、再哈希法
H i = R H i ( k e y ) i = 1 , 2 , . . . , k H_i=RH_i(key) \ \ \ \ i=1,2,...,k Hi=RHi(key) i=1,2,...,k
R H I RH_I RHI均是不同的哈希函数,即在同义词产生地址冲突时计算另一个哈希函数地址,直到冲突不再发生。这种方法不易产生“聚集”,但增加了计算的时间。
3、链地址法
将所有关键字为同义词的记录存储在同一线性链表中,假设某哈希函数产生的哈希地址在区间
[
0
,
m
−
1
]
[0,m-1]
[0,m−1] 上,则设立一个指针型向量
C
h
a
i
n
C
h
a
i
n
H
a
s
h
[
m
]
Chain \ Chain \ Hash[m]
Chain Chain Hash[m]
其每个分量的初始状态都是空指针。凡哈希地址为 i i i的记录都插入到头指针为 C h a i n H a s h [ i ] Chain \ Hash[i] Chain Hash[i] 的链表中。在链表中的插入位置可以在表头或表尾;也可以在中间,以保持同义词在同一线性表中按关键字有序。
例:已知一组关键字为(67,84,18,26,34,28),哈希函数为 H ( k e y ) = k e y % 7 H(key)=key\%7 H(key)=key%7 ,用链地址法处理冲突,试画出哈希表存储结构示意图。
已知:
哈希函数: H ( k e y ) = k e y % 7 H(key)=key\%7 H(key)=key%7
有冲突时,在对应链表中插入新节点
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/04b4b30ee9db907a8ab5aa4bc6b0879b.png)
4、公共溢出区法
这也是处理冲突的一种方法。假设哈希函数的值域为 [ 0 , m − 1 ] [0,m-1] [0,m−1] ,则设向量 H a s h T a b l e [ 0.. m − 1 ] HashTable[0..m-1] HashTable[0..m−1] 为基本表,每个分量存放一个记录,另设立向量 O v e r T a b l e [ 0.. v ] OverTable[0..v] OverTable[0..v] 为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表。
例:设有一组关键字如下:(67,84,18,26,34,28),哈希函数为 H ( k e y ) = k e y % 7 H(key)=key\%7 H(key)=key%7 ,用公共溢出区法处理冲突,试画出哈希表存储结构示意图。
已知:
哈希函数: H ( k e y ) = k e y % 7 H(key)=key\%7 H(key)=key%7
有冲突时,保存到公共溢出区
![CSDN图标](https://i-blog.csdnimg.cn/blog_migrate/7a9741d15f1c6bb74eee35afb0ec9a37.png)
四、在哈希表中查找元素
在哈希表上进行查找的过程和哈希造表的过程基本一致。给定 K K K 值,根据造表时设定的哈希函数求得哈希地址。
- 若表中此位置上没有记录,则查找不成功;
- 否则比较关键字,若和给定值相等,则查找成功;
- 否则根据造表时设定的处理冲突的方法找“下一地址”,直至哈希表中某个位置为“空”或者表中所填记录的关键字等于给定值时为止。