数据结构与算法分析之散列表

什么是散列表

散列表又叫哈希表(hash table), 是一种通过键(key) 而直接进行访问的数据结构.它不以关键字的比较为基本操作,采用直接寻址技术 (通过key映射到内存地址上去的)。在理想情况下,无须任何比较就可以找到待查关键字,查找的期望时间为O(1).
因此理想的散列表数据结构是一个包含一些项的具有固定大小的数组; 散列表的实现通常称为散列. 散列是一种用于以 常数平均时间 执行插入, 删除和查找的技术.

散列函数

把表的大小记作TableSize,将每个键映射到从0到TableSize-1这个范围中的某个数,并将其放到适当的单元中, 这个映射就叫做散列函数.

散列表的冲突现象

理想情况下散列函数应该运算简单并且应该保证任何两个不同的键映射到不同的单元, 但实际上无法做到.
两个不同的key由于散列值相同而被映射到相同的单元, 称这种现象为冲突(Collision)或碰撞.发生冲突的两个关键字称为该散列函数的同义词(Synonym)。


最简单的两种解决冲突的方法

分离链接法
做法是将散列到同一个值的所有元素保存到一个链表中.
定义散列表的装填因子(load factor) λ 为散列表中的元素个数与散列表的大小的比值;
散列表的大小实际上并不重要, 而装填因子才是重要的.
分离链接散列法的一般法则是使得表的大小尽量与预料的元素个数差不多(即λ=1).
使表的大小是一个素数以保证一个好的分布, 这也是一个好的想法.

开放定址法
不同于分离链接法, 开放定址法是当冲突发生时, 就尝试选择另外一个单元, 直到找到空单元.
更正式的, 单元h0(x), h1(x), h2(x), ...依次进行试选, 其中hi(x)=(hash(x)+f(i)) mod TableSize, 且f(0) = 0. 函数f是解决冲突函数.
因为所有数据都要置入表内, 所以使用这个方案所需要的表要比分离链接散列需要的表达. 
一般来说, 对不使用分离链接法的散列表来说, 其装填因子应该低于λ=0.5. 这样的表叫做探测散列表. 下面是三种常见的解决冲突的方法.


1.线性探测
在线性探测中, 函数f是i的线性函数, 一般情况下f(i) = i, 这相当于逐个探测每个单元(使用回绕)来查找出空单元.
这种方法会使一些占据的单元形成区块, 其结果成为一次聚集(primary clustering), 
这种方法对于插入和不成功的查找来说大约为1/2(1 + 1 / (1 - λ)^2),而对于成功的查找来说则是1/2(1 + 1/(1-λ))

2.平方探测
平方探测就是冲突函数为二次函数的探测方法.流行的选择是f(i)=i^2.
对于这种解决办法, 如果发生冲突时, 首先考虑i=1, 此时i^2=1, 即冲突位置的下一个位置, 如果该位置为空, 则插入, 如果不为空, 
则下一个探测单元在距冲突位置为i^2即2^2=4远处, 如果为空则插入, 否则继续探测距冲突位置9, 16, 25...远处

一个重要的定理:
如果使用平方探测, 且表的大小是素数,那么当表至少有一般是空的时候, 总能够插入一个新的元素

在探测散列表中标准的删除操作不能执行, 因为相应单元可能已经引起冲突,元素绕过它存储在别处. 如果删除一个, 那么可能剩下的find操作都是失败.
因此探测散列表需要懒惰删除.

虽然平方探测排除了一次聚集, 但是散列到同一位置上的那些元素将探测相同的设备单元, 这称为二次聚集(secondary clustering). 二次聚集理论上是小缺憾, 模拟结果
指出, 对每次查找, 它一般要引起另外的少于一半的探测.

3.双散列
对于双散列, 一种流行的选择是f(i)=i*hash2(x). 该公式是说, 将第二个散列函数应用到x并在距离hash2(x), 2hash2(x),......等处探测.hash2(x)选择得不好将会很糟糕.
诸如hash2(x)=R-(x mod R)这样的函数将起到良好的作用,其中R为小于TableSize的素数.

在使用双散列时, 保证表的大小为素数是非常重要的.
如果双散列正确实现, 模拟表明, 预期的探测次数几乎和随机冲突解决方法的情形相同.这使得双散列理论上很有吸引力.
不过平方探测不需要使用第二个散列函数, 从在实践中可能更简单并且快.当键为字符串时,尤其如此, 因为其散列函数计算很耗时.

再散列

当表中的元素达到一定条件后(通常为性能下降), 新建一个表, 将原表迁移过去的操作

再散列可以用平方探测以多种方法实现.
1.只要表填到一般就再散列
2.只有当插入失败时才散列, 这是一种极端的做法.
3.途中(middle-of-road)策略: 当表达到某一个装填因子时进行再散列.
由于随着装填因子的增加,标的性能的确在下降, 因此以好的截止点实现的第三种策略, 可能是最好的策略.


对于平方探测法:
建立另外一个大约两倍大的表(原表大小两倍后的第一个素数), 而且使用一个相关的新散列函数, 扫描整个原始散列表, 计算每个(未删除的)元素的新增散列值并将其插入到新表中


可扩散列










    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值