1、散列表介绍
-
散列表的英文叫
Hash Table
,我们平时也叫它哈希表
或者Hash 表
。散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来。可以说,如果没有数组,就没有散列表。
-
散列表是算法在时间和空间上作出权衡的例子,它介于两个极端之间:
- 如果没有内存限制,我们可以直接将键作为一个(可能非常庞大)数组索引,那么查找操作只需要一次。
- 如果没有时间限制,我们可以使用无序数组并进行顺序查找,这样就只需要很少的内存。
-
使用散列的查找算法分为两步。
- 第一步是用散列函数将被查找的键转化为数组的一个索引(可能出现哈希碰撞,即不同的键可以转化为相同的索引值)
- 第二步是处理碰撞冲突的过程。有两种常用的解决方法:拉链法和线性探测法(开放寻址法的一种)
2、散列函数
- 散列函数的定义
- 一个把查找表中的关键字映射成该关键字对应的地址的函数,记为
Hash(key)=Addr
(这里的地址可以是数组下标、索引或内存地址等)。
- 一个把查找表中的关键字映射成该关键字对应的地址的函数,记为
- 散列函数的构造方法
-
直接定址法
直接取关键字的某个线性函数值为散列地址,散列函数为:H(key)=key
或H(key)=a*key±b
。式中,a
和b
是常数。这种计算方法简单且不会发生冲突。但只适合关键字的分布基本连续的情况。 -
除留余数法
这是一种同样简单、最常用的方法,假定散列表表长为m
,取一个不大于m
但最接近或等于m
的质数p
,散列函数为:H(key)=key%p
或H(key)=key MOD p
(MOD
是取模运算符)模p
为最接近m
的质数时造成冲突的可能性最小。
-
数字分析法
比如学号一个班的同学前几位都是2022030XX
,后两位每种数码(0
,1
,2
…)出现的机会均等,此时应选取数码分布较为均匀的若干位作为散列地址。该方法适合于已知关键字集合,若更换了关键字(身份号),则需要重新构造新的散列函数。 -
其他
- 浮点数:例如将键转换为二进制再使用除留余数法。
- 字符串:例如将键转换为大整数(例如把字符串当作N位的R进制值),然后再使用除留余数法。
- 组合键。
- 自定义的
hashCode
方法。 - …
-
3、哈希碰撞的处理
- 基于拉链法的散列表
散列表中,每个桶(bucket)
或者槽(slot)
会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中:
- 线性探测(Linear Probing)
-
插入:如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。
-
查找:过程和插入一样,找到对应数组下标后,对比x与数组中存储的值是否相等,若不等则依次往后查找…
-
删除:删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为 deleted 的空间,并不是停下来,而是继续往下探测。
-
4、参考文献
- 数据结构☞散列表
- 散列(Hash)表
- 算法(第四版) 3.4散列表