一、哈希表的概念及作用
一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。
理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。
哈希表最常见的例子是以学生学号为关键字的成绩表,1号学生的记录位置在第一条,10号学生的记录位置在第10条...
如果我们以学生姓名为关键字,如何建立查找表,使得根据姓名可以直接找到相应记录呢?
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
p
q
r
s
t
u
v
w
x
y
z
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
刘丽
刘宏英
吴军
吴小艳
李秋梅
陈伟
...
姓名中各字拼音首字母
ll
lhy
wj
wxy
lqm
cw
...
用所有首字母编号值相加求和
24
46
33
72
42
26
...
最小值可能为3 最大值可能为78 可放75个学生
用上述得到的数值作为对应记录在表中的位置,得到下表:
成绩一
成绩二...
3
...
...
...
24
刘丽
82
95
25
...
26
陈伟
...
...
33
吴军
...
...
42
李秋梅
...
...
46
刘宏英
...
...
72
吴小艳
...
...
78
...
上面这张表即哈希表。
如果将来要查李秋梅的成绩,可以用上述方法求出该记录所在位置:
李秋梅:lqm 12+17+13=42 取表中第42条记录即可。
问题:如果两个同学分别叫 刘丽 刘兰 该如何处理这两条记录?
这个问题是哈希表不可避免的,即冲突现象:对不同的关键字可能得到同一哈希地址。
二、哈希表的构造方法
1、直接定址法
例如:有一个从1到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。
地址
01
02
...
25
26
27
...
100
年龄
1
2
...
25
26
27
...
...
人数
3000
2000
...
1050
...
...
...
...
...
2、数字分析法
有学生的生日数据如下:
年.月.日
75.10.03
75.11.23
76.03.02
76.07.12
75.04.21
76.02.15
...经分析,第一位,第二位,第三位重复的可能性大,取这三位造成冲突的机会增加,所以尽量不取前三位,取后三位比较好。
3、平方取中法
取关键字平方后的中间几位为哈希地址。
4、折叠法
将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。
例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作关键字建立一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。如果一本书的编号为0-442-20586-4,则:
5864
5864
4220
0224
+)
04
+)
04
-----------
-----------
10088
6092
H(key)=0088
H(key)=6092
(a)移位叠加
(b)间界叠加
5、除留余数法
取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。
H(key)=key MOD p (p<=m)
6、随机数法
选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
H(key)=random(key) ,其中random为随机函数。通常用于关键字长度不等时采用此法。
三、总结
哈希表的优缺点
哈希表的优点
1. 速度快:不论表中有多少数据,插入删除和查找都只需要接近常量的时间0(1)(wikipedia原文:
Hash tables support the efficient lookup, insertion and deletion of elements in constant time on average (O(1)) that does not vary with the number of elements stored in the table;)
2. 编程实现相对容易(相对树std::map而言)。
哈希表的缺点
1. 基于数组:数组创建后难于扩展;某些哈希表被基本填满时,性能下降得非常严重,所以程序虽必须要清楚表中将要存储多少数据(或者准备好定期地把数据转移到更大的哈希表中,这是个费时的过程)。
2. 难于遍历:没有一种简便的方法可以以任何一种顺序〔例如从小到大〕遍历表中数据项。如果需要这种能力,就只能选择其他数据结构。
何时选择哈希表
1. 如果不需要有序遍历数据,并且可以提前预测数据量的大小,那么哈希表在速度和易用性方面是无与伦比的。
2. 如果需要在一秒种内查找上千条记录通常使用哈希表(例如拼写检查器或者数据库);哈希表的速度明显比树快,树的操作通常需要O(N)的时间级。
哈希表基本原理
1. 准备大数组用以存储用户数据,数组索引称之为哈希键(hash key)
2. 通过将用户键(user key)转换为哈希键(hash key),并在数组中找寻哈希键对应之数据(bucket)。
3. 通常hash键做取模操作以转换为正确范围之数组索引
哈希键冲突
理论上通过某种方法(哈希函数hash function)可以使得用户键与哈希键一一对应,但实际上很难找到这样的哈希算法(只能接近,wikipedia原文:A nonzero collision probability is inevitable in any hash implementation)。
如果某种哈希算法使得两个用户键对应同一个哈希键,这种状况被称之为键冲突(wikipedia原文:in some cases, the calculated index can be the same for two different keys (a "collision"))
如何解决冲突
1. 开放定址法
2. 再哈希法:当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。
3. 链地址法:将所有关键字为同义词的记录存储在同一线性链表中;
哈希表设计难点
1. 哈希表的大小多少合适?
2. 如何选择哈希算法尽可能减小冲突
3. 冲突时采用哪种解决方案
对于1,太大了可能浪费,太小了速度就慢;可以参考这篇文章http://www.wangchao.net.cn/bbsdetail_27962.html
对于3,冲突解决方案常用的有开放定址法(Open addressing)和链地址法(Separate chaining):
(wikipedia原文:There are a number of collision resolution techniques, but the most popular are open addressing and chaining.)
链地址法比较常用,实作也简单;这也是为什么通常称哈希表为哈希链表的原因。(wikipedia原文:链地址vs开放地址的优缺点)
对于2,主要从算法引起冲突的次数要尽可能少和算法本身要快着手;可参考
经典的Hash算法的实现(源代码)
选择好的Hash函数