散列(哈希)表

Definition

散列(哈希)函数

通过一个**散列函数(哈希函数)**将关键字映射到对应的关键字的地址的函数记为:
H a s h ( k e y ) = A d d r Hash(key)=Addr Hash(key)=Addr
其中,地址 A d d r Addr Addr 既可以是数组下标也可以是索引或者内存地址

冲突

因为映射的值域是有限的,通常远小于定义域,也就是说,散列表可能会把两个或两个以上的关键字映射到同一个地址上,这种情况称为冲突,发生碰撞的不同关键字称为同义词

散列(哈希)表

根据关键字进行直接访问的数据结构,建立了关键字和存储地址之间的一种直接映射关系

构造散列函数

直接定址法

取关键字的线性函数值:
H ( k e y ) = a × k e y + b H(key)=a\times key+b H(key)=a×key+b
其中, a , b a,b a,b 都是常数。这种方法非常简单,并且是一个一一映射,不会产生冲突,适合关键字的分布基本连续的情况,否则会产生大量空位造成空间浪费

除留余数法

最简单,最常用的方法,取一个不大于列表长度但是最接近的一个质数 p p p,利用以下公式把关键字转换成散列地址:
H ( k e y ) = k e y % p H(key)=key\%p H(key)=key%p
选好 p p p 可以减少冲突,使得每个关键字通过函数转换之后等概率的分布在散列空间上

数字分析法

根据关键字某一位数的数码作为散列地址,例如十进制数,可以选取个位数作为散列地址,散列空间为 [ 0 , 9 ] [0,9] [0,9]。需要注意的是,每个位置上的数码的分布不一定是均匀的,比如在某个位置上可能只有几个数码出现的频率较大,而在一些位置上每个数码分布都比较均匀,这时我们需要选取数码分布较为均匀的若干位作为散列地址,减少冲突。

平方取中法

将关键字取平方之后选取中间若干位作为散列地址,具体情况具体分析。得到的散列地址和关键字的每一位都有关系,因此散列地址分布比较均匀,适合关键字每个位取值都不太均匀的情况,或者均小于散列地址所需的位数。

折叠法

将关键字分割成位数相同的几个部分,取叠加和作为散列地址,适用于关键字位数很多并且数字分布均匀的情况

冲突处理

开放定址法

空闲地址可以向非同义词开放,也就是说,当发生冲突的时候,可以查看其他空闲的地址,存放这个与原来地址冲突的关键字。递推公式:
H i = ( H ( k e y ) + d i ) % m H_{i}=(H(key)+d_{i})\%m Hi=(H(key)+di)%m
其中, i = 0 , 1 , 2 ⋯   , k ( k ≤ m − 1 ) i=0,1,2\cdots,k(k\le m-1) i=0,1,2,kkm1 m m m 为散列表表长; d i d_{i} di 为增量序列。取定增量之后,处理方法是确定的,通常有:

  • 线性探测法:取一个线性增量,即 d i = 0 , 1 , 2 , ⋯   , m − 1 d_{i}=0,1,2,\cdots,m-1 di=0,1,2,,m1,顺序查看表中的下一个单元(当探测到表尾地址 m − 1 m-1 m1 时,下一个地址是 0 0 0 )直到找到一个空闲的单元,否则遍查全表。这种做法使得本来应该存放在地址 i i i 上的元素存放到地址 i + 1 i+1 i+1 上,以此类推,后续元素不断竞争位置,容易造成元素堆积在相邻的地址,降低查找效率(即同义词顺序存储,查找时变为顺序查找,效率极差)
  • 平方探测法:即 d i = 0 2 , 1 2 , − 1 2 , 2 2 . − 22 , ⋯   , k 2 , − k 2 d_{i}=0^{2},1^{2},-1^{2},2^{2}.-2{2},\cdots,k^{2},-k^{2} di=02,12,12,22.22,,k2,k2,其中 k ≤ m / 2 k\le m/2 km/2,散列表长度必须时一个可以表示成 4 k + 3 4k+3 4k+3 的一个素数,可以避免堆积,但是无法探测所有单元,可以探测至少一半
  • 再散列探测法:再使用一个哈希函数进行映射,即 d i = H a s h 2 ( k e y ) d_{i}=Hash_{2}(key) di=Hash2(key),又称为双散列法,当通过第一个散列函数发生冲突时,使用第二个哈希函数计算地址增量:
    H i = ( H ( k e y ) + i × H 2 ( k e y ) ) % m H_{i}=(H(key)+i\times H_{2}(key))\%m Hi=(H(key)+i×H2(key))%m
    其中, i i i 是冲突次数,初始为 0 0 0。最多经过 m − 1 m-1 m1 次探测就可以遍历全部。

拉链法 Chaining

很简单,当发生冲突的时候,把同义词存放在一个线性链表里面,散列地址唯一标识这个线性链表的表头,适用于经常插入删除的情况

About Performance

查找:
A d d r = H a s h ( k e y ) Addr=Hash(key) Addr=Hash(key)
一种是:直接映射后判断是否有记录,无记录返回失败,有则比较关键字的值,相等返回成功;另一种是通过给定的处理冲突的方法计算下一个散列地址,查找记录。例如散列函数 H ( k e y ) = k e y % 13 H(key)=key\%13 H(key)=key%13,采用线性探测法增量为 1 1 1 的散列表:

哈希地址0123456789101112131415
关键字值140168275519208479231110

查找84:

  • 计算哈希值 H ( 84 ) = 6 H(84)=6 H(84)=6 L ( 6 ) L(6) L(6) 不空并且关键字不相等
  • 计算下一个哈希地址值 H 1 = ( 6 + 1 ) % 16 = 7 H_{1}=(6+1)\%16=7 H1=(6+1)%16=7 (不是13,是表长16),不空且不相等
  • 计算下一个哈希地址值 H 2 = ( 7 + 1 ) % 16 = 8 H_{2}=(7+1)\%16=8 H2=(7+1)%16=8,不空且相等,返回记录序号为 8 8 8

散列表的查找效率取决于三个因素:

  • 散列函数
  • 冲突处理方法
  • 装填因子

装填因子

一般记为 α \alpha α ,定义散列表的装满程度,即:
α = n m \alpha=\frac{n}{m} α=mn
其中, n n n 为表中记录的数量, m m m 为散列表长度。散列表的装满程度直接以来于 α \alpha α 而不直接依赖于 n , m n,m n,m。可以看出 α \alpha α 越大,记录越满,冲突可能性越大。
不同冲突处理方法对应的查找时间复杂度如下:

冲突处理方法时间复杂度
查找成功查找失败
线性探测法S≈1/2(1+1/(1-α))U≈1/2(1+1/(1-α)^2)
二次探测法和双哈希法S≈-1/α+ln(1-α)U≈1/(1-α)
拉链法S≈1+α/2U≈α+e^{-α}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值