除留余数法构造哈希表_四、哈希表hash table

7f9cc0bbbcec1df15b48bec4243e157d.png

本文目录:

1.字典与哈希表
2.hash构造方法
3.hash冲突及解决
4.冲突解决:开放定址法
5.如何避免冲突:小的装填因子和好的散列函数

1.字典与哈希表

字典的内部结构是怎样的呢?PyDictObject对象就是dict的内部实现。

(1)哈希表 (hash tables)

哈希表(也叫散列表),根据关键值对(Key-value)而直接进行访问的数据结构。它通过把key和value映射到表中一个位置来访问记录,这种查询速度非常快,更新也快。而这个映射函数叫做哈希函数,存放值的数组叫做哈希表。 哈希函数的实现方式决定了哈希表的搜索效率。具体操作过程是:

  1. 数据添加:把key通过哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
  2. 数据查询:再次使用哈希函数将key转换为对应的数组下标,并定位到数组的位置获取value。

但是,对key进行hash的时候,不同的key可能hash出来的结果是一样的,尤其是数据量增多的时候,这个问题叫做哈希冲突。如果解决这种冲突情况呢?通常的做法有两种,一种是链接法,另一种是开放寻址法,Python选择后者。

(2)开放寻址法(open addressing)

开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。

set集合和dict一样也是基于散列表的,只是他的表元只包含值的引用而没有对键的引用,其他的和dict基本上是一致的,所以在此就不再多说了。

>>>hash('test')            # 字符串
2314058222102390712
>>> hash(1)                 # 数字
1
>>> hash(str([1,2,3]))      # 集合
1335416675971793195
>>> hash(str(sorted({'1':1}))) # 字典
7666464346782421378
>>>

(3)字典dict操作

>>> dict1 = {'a': 1, 'b': 2, 'b': '3'}
>>> dict1['b'] # 根据key取出值value
'3'
>>> dict1
{'a': 1, 'b': '3'}
>>>list(dict1 .keys())[list(dict1 .values()).index(1)] # 
'a' # 根据value去除key
>>> dict1['d']=4 #增加新的元素
>>> del dict1['d'] #删除元素
>>> dict1.keys() # 以列表返回一个字典所有的键
>>> dict1.values() # 以列表返回字典中的所有值
>>> dict1.items() # 以列表返回可遍历的(键, 值) 元组数组

2.hash构造方法

除留余数法最为常用:

1. 直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a?key + b,其中a和b为常数(这种散列函数叫做自身函数)

2. 数字分析法:分析一组数据,比如一组员工的出生年月日,这时我们发现出生年月日的前几位数字大体相同,这样的话,出现冲突的几率就会很大,但是我们发现年月日的后几位表示月份和具体日期的数字差别很大,如果用后面的数字来构成散列地址,则冲突的几率会明显降低。因此数字分析法就是找出数字的规律,尽可能利用这些数据来构造冲突几率较低的散列地址。

3. 平方取中法:取关键字平方后的中间几位作为散列地址。

4. 折叠法:将关键字分割成位数相同的几部分,最后一部分位数可以不同,然后取这几部分的叠加和(去除进位)作为散列地址。

5. 随机数法:选择一随机函数,取关键字的随机值作为散列地址,通常用于关键字长度不同的场合。

6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。即 H(key) = key MOD p, p<=m。不仅可以对关键字直接取模,也可在折叠、平方取中等运算之后取模。对p的选择很重要,一般取素数或m,若p选的不好,容易产生同义词。

3.hash冲突及解决

hash冲突在所难免,解决冲突是一个复杂问题。冲突主要取决于:

(1)与散列函数有关,一个好的散列函数的值应尽可能平均分布。

(2)与解决冲突的哈希冲突函数有关。

(3)与负载因子的大小。太大不一定就好,而且浪费空间严重,负载因子和散列函数是联动的。

解决冲突的办法:

(1)开放定址法:线性探查法、平方探查法、伪随机序列法、双哈希函数法。

(2)拉链法:把所有同义词,即hash值相同的记录,用单链表连接起来。最简单的办法如下:如果两个键映射到了同一个位置,就在这个位置存储一个链表。链表短还行,查找不慢;但链表很长就悲剧了

(1)冲突解决:开放定址法

当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。基本公式为:hash(key) = (hash(key)+di)mod TableSize。其中di为增量序列,TableSize为表长。根据di的不同我们又可以分为线性探测,平方(二次)探测,双散列探测。

1)线性探测

以增量序列 1,2,……,(TableSize -1)循环试探下一个存储地址,即di = i。如果table[index+di]为空则进行插入,反之试探下一个增量。但是线性探测也有弊端,就是会造成元素聚集现象,降低查找效率。具体例子如下图: (这个例子中是除以11)

c07e9a4badca73f73f9fef6c1568289c.png

2)平方探测

以增量序列1,-1,4,-4…且q ≤ TableSize/2 循环试探下一个存储地址。

3)双散列探测

di 为i*h2(key),h2(key)是另一个散列函数。探测序列成:h2(key),2h2(key),3h2(key),……。对任意的key,h2(key) ≠ 0 !探测序列还应该保证所有的散列存储单元都应该能够被探测到。选择以下形式有良好的效果:

h2(key) = p - (key mod p)

其中:p < TableSize,p、TableSize都是素数。

(2)如何避免冲突

(1)较低的装填因子:即已占用的位置/总的位置数,一旦填装因子超过0.7,就该调整散列表的长度。

(2)良好的散列函数:良好的散列函数让数组中的值呈均匀分布。糟糕的散列函数让值扎堆,导致大量的冲突。

什么样的散列函数是良好的呢?你根本不用操心——天塌下来有高个子顶着。如果你好奇,可研究一下SHA函数。你可将它用作散列函数

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值