Hash冲突的四种解决办法

一.哈希表简介

非哈希表的特点:关键字在表中的位置和它自检不存在一个确定的关系,查找的过程为给定值一次和各个关系自进行比较,查找的效率取决于给定值进行比较的次数。
哈希表的特点:关键字在表中位置和它自检存在一种确定的关系。
哈希函数:一般情况下,需要在关键字与它在表中的存储位置之间建立一个函数关系,以(key)作为关键字为key的记录在表中的位置,通常称这个函数f(key)为哈希函数。
hash:翻译为”散列表“,就是把任意长度的输入,通过散列算法,变成固定长度输出,该输出结果是散列值。
这种转换是一种压缩映射,散列表的空间通常小鱼输入的空间,不同的输入可能会散列成相同的输出,所以不能从散列表来唯一的确定输入值。
简单的说就是一种将任意长度的消息压缩到固定长度的消息摘要函数。
hash冲突:就是根据key即经过一个函数f(key)得到的结果的作为地址去存放当前的key value键值对(这个是hashmap的存值方式),但是却发现算出来的地址上已经有人先来了。就是说这个地方要挤一挤啦。这就是所谓的hash冲突啦

二.哈希函数处理冲突的方法

1)开放定址法:

这种方法也称再散列法,其基本思想是:当关键字key的哈希地址p=H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,如果p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。这种方法有一个通用的再散列函数形式:

Hi=(H(key)+di)% m i=1,2,…,n

其中H(key)为哈希函数,m 为表长,di称为增量序列。增量序列的取值方式不同,相应的再散列方式也不同。主要有以下三种:

线性探测再散列

dii=1,2,3,…,m-1

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

二次探测再散列

di=12,-12,22,-22,…,k2,-k2 ( k<=m/2 )

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

伪随机探测再散列

di=伪随机数序列。

具体实现时,应建立一个伪随机数发生器,(如i=(i+p) % m),并给定一个随机数做起点。

例如,已知哈希表长度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 + 12)% 11 = 4,仍然冲突,再找下一个哈希地址为H2=(3 - 12)% 11 = 2,此时不再冲突,将69填入2号单元。

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

2) 再哈希法

这种方法是同时构造多个不同的哈希函数:

Hi=RH1(key) i=1,2,…,k

当哈希地址Hi=RH1(key)发生冲突时,再计算Hi=RH2(key)……,直到冲突不再产生。这种方法不易产生聚集,但增加了计算时间。

3)链地址法

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

4)建立公共溢出区

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

优缺点

开放散列表(open hashing)/拉链法(针对桶链结构
1)优点:①对于记录总数频繁可变的情况,处理的比较好(也就是避免了动态调整的开销)由于记录存储在结点中,而结点是动态分配,不会造成内存的浪费,所以尤其适合那种记录本身尺寸(size)很大的情况,因为此时指针的开销可以忽略不计了删除记录时,比较方便,直接通过指针操作即可
2)缺点: ①存储的记录是随机分布在内存中的,这样在查询记录时,相比结构紧凑的数据类型(比如数组),哈希表的跳转访问会带来额外的时间开销 ②如果所有的 key-value 对是可以提前预知,并之后不会发生变化时(即不允许插入和删除),可以人为创建一个不会产生冲突的完美哈希函数(perfect hash function),此时封闭散列的性能将远高于开放散列 ③由于使用指针,记录不容易进行序列化(serialize)操作
封闭散列(closed hashing)/ 开放定址法
1)优点: ①记录更容易进行序列化(serialize)操作 ②如果记录总数可以预知,可以创建完美哈希函数,此时处理数据的效率是非常高的

2)缺点: ①存储记录的数目不能超过桶数组的长度,如果超过就需要扩容,而扩容会导致某次操作的时间成本飙升,这在实时或者交互式应用中可能会是一个严重的缺陷 ②使用探测序列,有可能其计算的时间成本过高,导致哈希表的处理性能降低 ③由于记录是存放在桶数组中的,而桶数组必然存在空槽,所以当记录本身尺寸(size)很大并且记录总数规模很大时,空槽占用的空间会导致明显的内存浪费 ④删除记录时,比较麻烦。比如需要删除记录a,记录b是在a之后插入桶数组的,但是和记录a有冲突,是通过探测序列再次跳转找到的地址,所以如果直接删除a,a的位置变为空槽,而空槽是查询记录失败的终止条件,这样会导致记录b在a的位置重新插入数据前不可见,所以不能直接删除a,而是设置删除标记。这就需要额外的空间和操作。

转载于:https://www.cnblogs.com/gongcheng-/p/10894205.html

<think>好的,我现在需要回答用户关于哈希冲突解决方法及Java HashMap的实现方式的问题。首先,我得回忆一下哈希冲突的基本概念。哈希冲突是指不同的键通过哈希函数计算后得到相同的哈希值,导致它们在哈希表中的位置冲突。解决这个问题的方法主要有几种,比如开放定址法、链地址法、再哈希法、建立公共溢出区等。 用户提到了Java中的HashMap,我需要确定它具体使用哪种方法。根据之前的引用资料,引用[2]和[3]都提到HashMap采用的是链地址法,也就是每个哈希桶(bucket)中使用链表来存储冲突的键值对。不过Java 8之后,当链表长度超过阈值(默认是8)时,链表会转换成红黑树,以提高查询效率。这一点需要特别说明,因为它是对传统链地址法的优化。 接下来,我需要整理解决哈希冲突的主要方法,并逐一解释。比如开放定址法包括线性探测、平方探测等,而链地址法则是将冲突的元素存储在同一个位置的链表中。再哈希法则使用多个哈希函数来减少冲突的可能性。公共溢出区则是一个独立区域存放冲突元素。 然后,结合HashMap的具体实现,详细说明链地址法如何工作。比如当发生冲突时,新的键值对会被添加到对应桶的链表末尾,而当链表过长时转换为红黑树。同时,可以提到负载因子和扩容机制,比如当元素数量超过容量乘以负载因子时,哈希表会扩容,减少冲突概率。 还需要对比其他方法,比如开放定址法和链地址法的优缺点。例如,开放定址法可能更容易产生聚集现象,而链地址法在负载因子较高时仍然能有效工作,但需要额外的空间存储指针。 另外,引用[4]中提到了HashMap和TreeMap的区别,虽然用户的问题不直接涉及TreeMap,但可以简要说明HashMap的哈希桶结构,而TreeMap使用红黑树实现有序存储,但这里的重点还是HashMap如何处理冲突。 在组织内容时,需要分点清晰,先介绍哈希冲突的概念,再列举解决方法,最后详细说明HashMap的具体实现,包括链地址法和树化策略。同时,确保使用正确的中文表达,避免技术术语错误。最后,生成相关问题时要围绕哈希冲突解决方法和HashMap的特性展开,比如性能优化、其他解决方法的比较等。 检查是否所有数学公式都正确使用$...$或$$...$$格式,但当前问题主要涉及数据结构,可能数学公式较少,但如果有涉及的地方需要注意格式。例如,当提到时间复杂度O(1)或O(logN)时,应使用$O(1)$和$O(\log N)$的格式。不过根据用户示例中的引用,可能直接使用原文中的格式,比如引用[4]中的O(logN),需要保持一致性。 最后,确保引用标识正确添加,比如链地址法的说明引用[2]和[3],树化策略引用[2],负载因子和扩容引用[1]和[2]。同时,回答结构要清晰,分步骤或分点说明,帮助用户逐步理解。</think>### 解决哈希冲突的方法及Java HashMap的实现方式 #### 一、哈希冲突的定义 哈希冲突指不同的键(Key)通过哈希函数计算后得到相同的哈希值,导致它们在哈希表中的存储位置冲突。例如,两个不同的键$k_1$和$k_2$满足$hash(k_1) = hash(k_2)$。 #### 二、解决哈希冲突的主要方法 1. **开放定址法** - 当冲突发生时,按特定规则(如线性探测、平方探测)寻找下一个空闲位置存储数据。 - 例如:线性探测公式为$hash'(k) = (hash(k) + i) \% m$,其中$i$为探测次数,$m$为表长。 2. **链地址法(Chaining)** - 每个哈希桶(Bucket)维护一个链表(或树结构),冲突的键值对直接添加到链表中[^2][^3]。 3. **再哈希法(Rehashing)** - 定义多个哈希函数,当冲突发生时,使用第二个哈希函数重新计算位置,直到找到空闲位置。 4. **公共溢出区法** - 将冲突的键值对统一存放到一个独立的溢出区域中。 --- #### 三、Java HashMap的实现方式 1. **链地址法为基础** - HashMap默认使用链表处理冲突,每个哈希桶对应一个单向链表,冲突的键值对按插入顺序链接[^3]。 - **示例**:若键$k_1$和$k_2$哈希值相同,则它们会被存储在同一个桶的链表中。 2. **树化优化(Java 8+)** - 当链表长度超过阈值(默认8)且哈希表容量≥64时,链表会转换为红黑树,将查询时间复杂度从$O(n)$优化为$O(\log n)$。 3. **负载因子扩容机制** - 负载因子(默认0.75)控制哈希表扩容的时机。当元素数量超过`容量×负载因子`时,哈希表会扩容为原来的2倍,重新分配键值对以减少冲突概率[^1]。 4. **哈希函数优化** - HashMap通过扰动函数(如异或运算)混合键的哈希值的高位和低位,降低冲突概率。 - 公式示例: ```java static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } ``` --- #### 四、其他方法的对比 | 方法 | 优点 | 缺点 | |--------------|-------------------------------|-------------------------------| | 链地址法 | 高负载因子下仍高效 | 需额外空间存储指针 | | 开放定址法 | 无需额外数据结构 | 易产生聚集现象,删除操作复杂 | | 再哈希法 | 冲突概率低 | 计算成本高 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值