美文网首页
如何解决哈希冲突

如何解决哈希冲突

作者: Djbfifjd | 来源:发表于2021-01-19 17:08 被阅读0次

一、简述

通过构造性能良好的哈希函数,可以减少冲突,但一般不可能完全避免冲突,因此解决冲突是哈希法的另一个关键问题。创建哈希表和查找哈希表都会遇到冲突,两种情况下解决冲突的方法应该一致

二、拉链法

基本思想是将所有哈希地址为 i 的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第 i 个单元中,因而查找、插入和删除主要在同义词链中进行。拉链法适用于经常进行插入和删除的情况。

1️⃣HashMap、HashSet 其实都是采用的拉链法来解决哈希冲突的,就是在每个位桶实现的时候,采用链表(jdk1.8之后采用链表+红黑树)的数据结构来去存取发生哈希冲突的输入域的关键字(也就是被哈希函数映射到同一个位桶上的关键字)。首先来看使用拉链法解决哈希冲突的几个操作:

  1. 插入操作:在发生哈希冲突的时候,输入域的关键字去映射到位桶(实际上是实现位桶的这个数据结构,链表或者红黑树)中去的时候,先检查待插入元素 x 是否出现在表中,很明显,这个查找所用的次数不会超过装载因子(n/m:n 为输入域的关键字个数,m 为位桶的数目),它是个常数,所以插入操作的最坏时间复杂度为 O(1) 的。

  2. 查询操作:和插入操作一样,在发生哈希冲突的时候,去检索的时间复杂度不会超过装载因子,也就是检索数据的时间复杂度也是 O(1) 的。

  3. 删除操作:如果在拉链法中想要使用链表这种数据结构来实现位桶,那么这个链表一定是双向链表,因为在删除一个元素 x 的时候,需要更改 x 的前驱元素的 next 指针的属性,把 x 从链表中删除。这个操作的时间复杂度也是 O(1) 的。

2️⃣与开放定址法相比,拉链法有如下几个优点:

  1. 拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短。
  2. 由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况。
  3. 开放定址法为减少冲突,要求装填因子 α 较小,故当结点规模较大时会浪费很多空间。而拉链法中可取 α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间。
  4. 在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。

3️⃣拉链法的缺点

指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度。

三、开放定址法

开放地址法有个非常关键的特征,就是所有输入的元素全部存放在哈希表里,换句话说,位桶的实现是不需要任何的链表来实现的,也就是这个哈希表的装载因子不会超过 1。它的实现是在插入一个元素的时候,先通过哈希函数进行判断,若是发生哈希冲突,就以当前地址为基准,根据再寻址的方法(探查序列),去寻找下一个地址,若发生冲突再去寻找,直至找到一个为空的地址为止。所以这种方法又称为再散列法。

基本思想是:当关键字 key 的哈希地址 p=H(key) 出现冲突时,以 p 为基础,产生另一个哈希地址 p1,如果 p1 仍然冲突,再以 p 为基础,产生另一个哈希地址 p2,…直到找出一个不冲突的哈希地址 pi,将相应元素存入其中。这种方法有一个通用的再散列函数形式:Hi=(H(key) +di)% m i=1,2,…,n。其中 H(key) 为哈希函数,m 为表长,di 称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

1️⃣线性探查再散列

特点:冲突发生时,顺序查看表中下一单元,直到找出一个空单元或查遍全表。

使用例子:ThreadLocal 里面的ThreadLocalMap

2️⃣二次探查再散列

特点:冲突发生时,在表的左右进行跳跃式探测,比较灵活。

3️⃣伪随机探测再散列di=伪随机数序列
具体实现时,应建立一个伪随机数发生器,如 i=(i+p)%m,生成一个位随机序列,并给定一个随机数做起点,每次去加上这个伪随机数++就可以了。

4️⃣示例:

已知哈希表长度 m=11,哈希函数为:H(key)=key%11,则 H(47)=3,H(26)=4,H(60)=5,假设下一个关键字为 69,则 H(69)=3,与 47 冲突。

如果用线性探测再散列处理冲突,下一个哈希地址为 H1=(3+1)%11=4,仍然冲突。再找下一个哈希地址为 H2=(3+2)%11=5,还是冲突。继续找下一个哈希地址为 H3=(3+3)%11=6,此时不再冲突,将 69 填入 5 号单元。

如果用二次探测再散列处理冲突,下一个哈希地址为 H1=(3+1)%11=4,仍然冲突,再找下一个哈希地址为 H2=(3-1)%11=2,此时不再冲突,将 69 填入 2 号单元。

如果用伪随机探测再散列处理冲突,且伪随机数序列为:2,5,9,……..则下一个哈希地址为 H1=(3+2)%11=5,仍然冲突,再找下一个哈希地址为 H2=(3+5)%11=8,此时不再冲突,将 69 填入 8 号单元。

四、再散列法

就是在使用哈希函数去散列一个输入的时候,输出是同一个位置就再次散列,直至不发生冲突位置。这种方法是同时构造多个不同的哈希函数:Hi=RH1(key) i=1,2,…,k。当哈希地址 Hi 发生冲突时,再计算Hi=RH2(key)……直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

五、建立公共溢出区

基本思想:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表。

相关文章

  • 《恋上数据结构与算法一》笔记(十五)哈希表

    目录 哈希表 哈希冲突(Hash Collision) JDK1.8的哈希冲突解决方案 哈希函数 如何生成key的...

  • 《数据结构与算法》总结(六)哈希表

    目录 哈希表 哈希冲突(Hash Collision) JDK1.8的哈希冲突解决方案 哈希函数 如何生成key的...

  • 如何解决哈希冲突

    一、简述 通过构造性能良好的哈希函数,可以减少冲突,但一般不可能完全避免冲突,因此解决冲突是哈希法的另一个关键问题...

  • HashMap类简介

    1 基本定义 数据结构 数据结构数组链表红黑树用途存储键值对。数组下标为键的哈希值解决哈希冲突解决哈希冲突 定义参...

  • 解决哈希冲突

    解决哈希冲突的三种方法(拉链法、开放地址法、再散列法) https://blog.csdn.net/qq_3259...

  • go-map源码简单分析(map遍历为什么时随机的)

    GO 中map的底层是如何实现的 首先Go 语言采用的是哈希查找表,并且使用链表解决哈希冲突。 GO的内存模型 先...

  • HashMap

    结构:数组+链表+红黑树 (链表元素>8时变为红黑树)如何解决哈希冲突:链地址法哈希算法:与运算 实现原理Entr...

  • 哈希表—链地址法

    冰冻非一日之寒 哈希冲突是不可避免的,所以我们在设计哈希函数的同时,也要设计解决哈希冲突的办法。 哈希表本质就是一...

  • HashMap 1.7 死循环分析

    数据结构 HashMap 使用哈希表也叫散列表来存储数据的,哈希表为解决冲突,可以采用开放地址法、链地址法等来解决...

  • 数据结构5:散列(哈希)

    16.散列(哈希): 16.1:定义16.2:构造散列函数的几种方法 16.3:哈希冲突的解决方法 16.3....

网友评论

      本文标题:如何解决哈希冲突

      本文链接:https://www.haomeiwen.com/subject/hocczktx.html