目录
散列表(Hash)
一、背景与概念
散列表与线性表和树表的查找不同,后者是通过在表中进行一系列的关键字与给定值进行比较,达到检索的目的,关键字与其所对应的元素在表中的位置不存在映射关系,因此查找效率取决与给定值与关键字的比较次数。散列表却与之不同,散列表会根据关键字直接访问表中的数据,即关键字与其所在的元素在表中的位置存在映射关系。例如通过身份证号(身份证号作为关键字)可以找到这个人的信息。其中这种映射关系(规则) 叫做散列函数(也称”哈希函数“),它可以由用户自己定义。
二、散列函数
功能:把关键字映射成为一个地址,该地址上存储就是该关键字对应的元素(记录)
冲突:散列函数可能会把两个或者两个以上的不同关键字映射到同一地址,称这种情况为冲突
1. 散列函数的构造方法(部分常用方法)
构造前注意事项:
1)构造的散列函数定义域必须包含全部关键字,值域的范围取决于散列表的大小。
2)散列函数计算出来的地址尽可能均匀分布在整个地址空间,尽可能减少冲突发生。
3)散列函数尽可能的简单,在较短的时间内计算出任意一个关键字对应的地址。
1.1 直接定址法
直接取关键字的某个线性函数值作为散列地址,散列函数为:
H(key) = key 或 H(key) = a*key + b ,其中a,b为常数
特点:
① 计算简单,且不会产生冲突。
② 适合关键字的分布基本连续的情况,若分布不连续,则散列函数的值(地址)的间隔跨度大,对与存储空间来说,各个元素之间的空位就较多,造成存储空间浪费。
1.2 除留余数法
假定散列表表长为m,取一个不大于m但接近或等于m的质数p,利用以下公式把关键字转换成散列地址。散列函数为
H(key) = key % p
特点:
① 计算简单、最常用的方法
② 该方法的关键是选好p,使得每个关键字通过该散列函数计算出的地址尽可能等概率的分布在散列表对应的地址空间上,从而减少冲突
2. 处理冲突的方法
任何设计出来的散列函数都不可能绝对地避免冲突发生。为此,需要考虑当冲突发生时的处理方法,以下记录几种常用的处理方法【开放地址法】、【再哈希法】、【链地址法】。
我们把具有相同函数值(地址)的关键字对该哈希函数来说称为”同义词“
2.1 开放地址法
在插入数据时,对于同一个地址,若不同的关键字通过同一个Hash函数计算得到,那么后插入的元素会在原地址基础上再次通过hash函数计算得到结果加上一个增量得到的最终地址,该地址与原地址往往隔相邻或者相隔几个单元的长度(若最终单元上无内容,则插入,有内容则继续相同的方法找下一个单元插入…)这就是对于具有同义词或非同义词的关键字开所有地址,其数学地推公式为
H
i
=
(
H
(
k
e
y
)
+
d
i
)
%
m
H_i=(H(key)+d_i) \% m
Hi=(H(key)+di)%m
式中,H(key)为散列函数,
i
=
1
,
2
,
3...
,
k
(
k
<
=
m
−
1
)
i=1,2,3... ,k(k<=m-1)
i=1,2,3...,k(k<=m−1),di为增量序列。其中由d序列的取值方式不同分为三种方式:【线性探测法】、【二次探测法】、【伪随机探测再散列法】
2
2.1.1 线性探测法
当冲突发生时,顺序查看表中下一个单元(探测到表尾地址m-1时,下一个探测地址又回到表头地址0),直到期间直到一个空闲单元,则插入,若查遍全表,没有空闲空间,则表已满,探测结束。
缺点:这种方式容易产生堆积现象,例子:假设ki存在地址i上,再来一个元素kj,H(j)=i,出现冲突,就重新计算H(j)+d1=i+1,若第i+1位置上无元素,则kj存入,即kj元素本该存在i地址上,但却抢占了i+1地址,若再来一个元素本该存在i+1上,则它也只能抢占别的元素空间。若出现大量的同义词,则会大量聚集在第一次Hash得到的地址附近,这就是堆积现象,大大的降低查找效率。
2.1.2 二次探测法(又称:平方探测法)
二次探测法中增量序列di=12,-12,22,-22,…,k2,-k2,其中k<=m/2,散列表的长度m必须是一个可以表示成4k+3的素数。
优点: 平方探测法是一种处理冲突的较好的方法,可以避免出现”堆积“问题
缺点: 不能探测到散列表上的所有单元,但至少能探测到一半单元
2.1.3 伪随机探测法
增量序列di=伪随机序列
2.1 再哈希法
使用两个或多个不同的Hash函数,发生冲突时,用另一个Hash函数计算地址,直至冲突不在发生
优点:不容易产生”堆积现象“
缺点:多次计算增加时间消耗
2.2 链地址法
将所有关键字为同义词的元素存储在同一条线性链表中,散列表每个元素中增设一个指针域,指向该链表。
优点:非同一次不会产生冲突,无”堆积“现象,链表表长是动态申请,适合表长(表中元素个数不固定)不固定的场景
缺点:若同义词太多,就会产生太长的链表,影响查询效率