【哈希表】

什么是哈希表

  • 哈希表(Hash Table),也可以称为散列表或者 Hash 表
  • 哈希表用的是数组支持按照下标访问数据的特性,所以哈希表其实就是数组的一种扩展,由数组演化而来。
  • 是根据键(Key)而直接访问在内存存储位置的数据结构。
    过程:它通过映射函数 输入key 输出数组下标的方式,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做哈希函数,存放记录的数组称做哈希表

为什么需要哈希呢?

链表和哈希表做对比
比如 现在我们将每个人的性别作为数据进行存储,键为人名,值为对应的性别,其中 M 表示性别为男,F 表示性别为女。
为了和哈希表进行对比,我们先将这些数据存储在数组中。

查询数组中数据

为了和哈希表进行对比,我们先将这些数据存储在数组中。
在这里插入图片描述

此处准备了6个箱子(即长度为6的数组)来存储数据,假设我们需要查询 Ally 的性别,由于不知道 Ally 的数据存储在哪个箱子里,所以只能从头开始查询,这个操作便叫作线性查找。一般来说,我们可以把键当成数据的标识符,把值当成数据的内容。
在这里插入图片描述

从 0 号箱子开始查找,发现 0 号箱子中存储的键是 Joe 而不是 Ally,因此接着查找 1 号箱子。
哦豁,1 号箱子中的也不是 Ally,没办法,只能接着往下找。
有点小糟糕,2 号、3 号箱子中的也都不是 Ally。
在这里插入图片描述
功夫不负有心人,当我们查找到 4 号箱子的时候,发现其中数据的键为 Ally,把键对应的值取出,我们就知道 Ally 的性别为女(F)。

哈希表存储

通过上面的查找过程,我们发现数据量越多,线性查找耗费的时间就越长。由此可知:由于数据的查询较为耗时,所以此处并不适合使用数组来存储数据。
在这里插入图片描述
但使用哈希表便可以解决这个问题,首先准备好数组,这次我们用 5 个箱子的数组来存储数据。
在这里插入图片描述
尝试把 Joe 存进去,使用哈希函数(Hash)计算 Joe 的键,也就是字符串 Joe 的哈希值,比如得到的结果为4928。
在这里插入图片描述
将得到的哈希值除以数组的长度 5,求得其余数,这样的求余运算叫作mod运算,此处mod运算的结果为3。
在这里插入图片描述

因此,我们将 Joe 的数据存进数组的 3 号箱子中,重复前面的操作,将其他数据也存进数组中。
在这里插入图片描述
Sue 键的哈希值为 7291, mod 5 的结果为 1,将 Sue 的数据存进 1 号箱中。
在这里插入图片描述
Dan 键的哈希值为 1539, mod 5 的结果为 4,将 Dan 的数据存进 4 号箱中。
在这里插入图片描述
Nell 键的哈希值为 6276, mod 5 的结果为 1,本应将其存进数组的 1 号箱中,但此时 1 号箱中已经存储了 Sue 的数据,这种存储位置重复了的情况便叫作冲突(哈希冲突)
在这里插入图片描述
遇到这种情况,其中一种方法用链表法,用链表在已有数据的后面继续存储新的数据。
在这里插入图片描述
Ally 键的哈希值为 9143, mod 5 的结果为 3,本应将其存储在数组的 3 号箱中,但 3 号箱中已经有了 Joe 的数据,所以使用链表,在其后面存储 Ally 的数据。
在这里插入图片描述
Bob 键的哈希值为 5278, mod 5 的结果为 3,本应将其存储在数组的 3 号箱中,但 3 号箱中已经有了 Joe 和 Ally 的数据,所以使用链表,在 Ally 的后面继续存储 Bob 的数据。
在这里插入图片描述
像这样存储完所有数据,哈希表也就制作完成了。

查询哈希表中的数据

在这里插入图片描述
接下来讲解数据的查询方法,假设我们要查询 Dan 的性别。
在这里插入图片描述
为了知道 Dan 存储在哪个箱子里,首先需要算出 Dan 键的哈希值,然后对其进行 mod 运算,最后得到的结果为 4,于是我们知道了它存储在 4 号箱中。
在这里插入图片描述
查看 4 号箱可知,其中的数据的键与 Dan 一致,于是取出对应的值,由此我们便知道了 Dan 的性别为男(M)。
在这里插入图片描述
那么,想要查询 Ally 的性别时该怎么做呢?为了找到它的存储位置,先要算出 Ally 键的哈希值,再对其进行 mod 运算,最终得到的结果为 3。
在这里插入图片描述
然而 3 号箱中数据的键是 Joe 而不是 Ally,此时便需要对 Joe 所在的链表进行线性查找。
在这里插入图片描述
于是我们找到了键为 Ally 的数据,取出其对应的值,便知道了 Ally 的性别为女(F)。

哈希冲突的解决办法

链地址法

可以利用链表在存储数据后面加一个指针,指向后面冲突的数据来解决冲突,这种方法被称为链表法,也被称为链地址法。
有一组数据
19 01 23 14 55 68 11 86 37要存储在表长11的数组中,其中H(key)=key MOD 11
在这里插入图片描述

开放地址法(开放寻址法)

这种方法是指当冲突发生时,立刻计算出一个候补地址(数组上的位置)并将数据存进去。如果仍然有冲突,便继续计算下一个候补地址,直到有空地址为止,可以通过多次使用哈希函数或线性探测法等方法计算候补地址。

首先有一个H(key)的哈希函数

有三种取法
1)线性探测再散列
2)平方探测再散列
3)随机探测在散列(双探测再散列)

三种开放定址法解决冲突方案的例子
有一组数据
19 01 23 14 55 68 11 86 37要存储在表长11的数组中,其中H(key)=key MOD 11
那么按照上面三种解决冲突的方法,存储过程如下:
(表格解释:从前向后插入数据,如果插入位置已经占用,发生冲突,冲突的另起一行,计算地址,直到地址可用,后面冲突的继续向下另起一行。最终结果取最上面的数据(因为是最“占座”的数据))
线性探测再散列
我们取di=1,即冲突后存储在冲突后一个位置,如果仍然冲突继续向后

平方探测再散列
先走线性探测,如果冲突然后向这个点左探测,这样来回探测
在这里插入图片描述

随机探测在散列(双探测再散列) 发生冲突后 根据 伪随机函数 生成对应的随机数,就是他的索引,则有如下结果:
在这里插入图片描述

公共溢出区法

建立一个特殊存储空间,专门存放冲突的数据。此种方法适用于数据和冲突较少的情况。

再散列法

准备若干个hash函数,如果使用第一个hash函数发生了冲突,就使用第二个hash函数,第二个也冲突,使用第三个……
重点了解一下开放定制法和链地址法

哈希函数的构造方法

直接定制法

数字分析法

平方取中法

折叠法

除留余数法

H(key)=key MOD p (p<=m m为表长)
很明显,如何选取p是个关键问题。

使用举例
比如我们存储3 6 9,那么p就不能取3
因为 3 MOD 3 == 6 MOD 3 == 9 MOD 3
p应为不大于m的质数或是不含20以下的质因子的合数,这样可以减少地址的重复(冲突)

比如key = 7,39,18,24,33,21时取表长m为9 p为7 那么存储如下

index012345678
key721(冲突后移)243918(冲突后移)33(冲突后移)

随机数法 H(key) =Random(key) 取关键字的随机函数值为它的散列地址

哈希表的查找

查找过程和造表过程一致,假设采用开放定址法处理冲突,则查找过程为:
对于给定的key,计算hash地址index = H(key)
如果数组arr【index】的值为空 则查找不成功
如果数组arr【index】== key 则查找成功
否则 使用冲突解决方法求下一个地址,直到arr【index】== key或者 arr【index】==null

哈希表的删除

首先链地址法是可以直接删除元素的,但是开放定址法是不行的,拿前面的双探测再散列来说,假如我们删除了元素1,将其位置置空,那 23就永远找不到了。正确做法应该是删除之后置入一个原来不存在的数据,比如-1

备注:
借鉴了:
https://blog.csdn.net/u011109881/article/details/80379505
https://zhuanlan.zhihu.com/p/107326081

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aosis-AI

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值