哈希冲突的解决办法

1.开放寻址法

1.如果位置以及被占用了,就继续往后遍历,找到空的位置插入进去

2.查找的时候也是一样的,如果经过散列函数计算的位置是空闲的就是不存在,如果有元素,比较元素是否相等,然后继续往后遍历,如果到空位置都不相等就是无

3.对于删除操作,不能单纯的把删除的元素设置为空。在查找的时候,一旦我们通过线性探测方法,找到一个空闲位置,我们就可以认定散列表中不存在这个数据。但是,如果这个空闲位置是我们后来删除的,就会导致原来的查找算法失效。本来存在的数据,会被认定为不存在。我们可以将删除的元素,特殊标记为 deleted。当线性探测查找的时候,遇到标记为 deleted 的空间,并不是停下来,而是继续往下探测。

4.开放寻址法的问题是。当散列表中插入的数据越来越多,就会造成哈希冲突越来越高,极端情况下,我们可能需要探测整个散列表,所以最坏情况下的时间复杂度为 O(n)。同理,在删除和查找时,也有可能会线性探测整张散列表,才能找到要查找或者删除的数据。

上面的是线性探测

还有另外两种比较经典的探测方法,二次探测(Quadratic probing)和双重散列(Double hashing)。

所谓二次探测,跟线性探测很像,线性探测每次探测的步长是 1,那它探测的下标序列就是 hash(key)+0,hash(key)+1,hash(key)+2……而二次探测探测的步长就变成了原来的“二次方”,也就是说,它探测的下标序列就是 hash(key)+0,hash(key)+12,hash(key)+22……

所谓双重散列,意思就是不仅要使用一个散列函数。我们使用一组散列函数 hash1(key),hash2(key),hash3(key)……我们先用第一个散列函数,如果计算得到的存储位置已经被占用,再用第二个散列函数,依次类推,直到找到空闲的存储位置。

我们用装载因子来表示空位的占比

开放寻址法的优点:所有的数据都存在于数组中,有利于cpu的缓存机制来加快查询速度,序列化简单

缺点:删除麻烦,更容易造成hash冲突,装载因子不能太大,浪费内存

2.链表法

1.如果位置被填充,就在后面拉一条链表,插入到链表中。在散列表中,每个“桶(bucket)”或者“槽(slot)”会对应一条链表,所有散列值相同的元素我们都放到相同槽位对应的链表中。

当插入的时候,我们只需要通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可,所以插入的时间复杂度是 O(1)。当查找、删除一个元素时,我们同样通过散列函数计算出对应的槽,然后遍历链表查找或者删除。

那查找或删除操作的时间复杂度是多少呢?

这两个操作的时间复杂度跟链表的长度 k 成正比,也就是 O(k)。

优点:

1.链表法对内存的利用率比开放寻址法要高。因为链表结点可以在需要的时候再创建,并不需要像开放寻址法那样事先申请好。

2.链表法比起开放寻址法,对大装载因子的容忍度更高。开放寻址法只能适用装载因子小于 1 的情况。接近 1 时,就可能会有大量的散列冲突,导致大量的探测、再散列等,性能会下降很多。但是对于链表法来说,只要散列函数的值随机均匀,即便装载因子变成 10,也就是链表的长度变长了而已,虽然查找效率有所下降,但是比起顺序查找还是快很多。

缺点

链表因为要存储指针,所以对于比较小的对象的存储,是比较消耗内存的,还有可能会让内存的消耗翻倍。而且,因为链表中的结点是零散分布在内存中的,不是连续的,所以对 CPU 缓存是不友好的,这方面对于执行效率也有一定的影响。

### 哈希冲突解决方案概述 哈希表是一种高效的数据结构,用于存储键值对。然而,在实际使用中,由于哈希函数的限制或数据分布的原因,可能会出现哈希冲突的现象,即不同的键被映射到同一个位置。为了解决这一问题,有多种方法可供选择[^1]。 以下是几种常见的哈希冲突解决方案: #### 1. 开放寻址法 开放寻址法的核心思想是在发生冲突时,寻找下一个可用的空闲位置来存储数据。具体实现方式包括以下几种: - **线性探测**:当发生冲突时,按照固定的步长(通常是1)依次查找下一个位置,直到找到空闲位置[^3]。 - **平方探测**:与线性探测类似,但步长为平方数序列,例如 \( i^2 \),以减少聚集现象[^3]。 - **双重哈希**:使用第二个哈希函数计算步长,从而避免聚集问题。例如,假设第一个哈希函数为 \( h_1(k) = k \% m \),第二个哈希函数为 \( h_2(k) = c - (k \% c) \),其中 \( c < m \) 是一个常数[^4]。 #### 2. 链地址法 链地址法通过在每个哈希表槽位维护一个链表来解决冲突。当多个键被映射到同一个槽位时,这些键将被存储在该槽位对应的链表中。这种方法的优点是实现简单且易于扩展,但可能增加内存开销。 #### 3. 再哈希法 再哈希法的基本思想是使用多个哈希函数。当发生冲突时,尝试用另一个哈希函数重新计算地址,直到找到空闲位置[^3]。这种方法可以有效减少冲突概率,但需要额外设计多个哈希函数。 #### 4. 公共溢出区 公共溢出区方法将哈希表分为两部分:主表和溢出区。主表用于存储大部分数据,而溢出区用于存储冲突的数据。当主表中的某个槽位发生冲突时,冲突的元素会被存储到溢出区[^1]。这种方法可以提高空间利用率,但可能降低查询效率。 #### 5. 完美哈希 完美哈希是一种针对静态数据集的无冲突方案。它通过预先构建哈希函数,确保所有键都能被唯一映射到不同的槽位[^2]。虽然完美哈希在特定场景下非常高效,但它通常不适用于动态数据集。 --- ### 示例代码 以下是一个基于链地址法的哈希表实现示例: ```python class HashTable: def __init__(self, size): self.size = size self.table = [[] for _ in range(size)] def hash_function(self, key): return key % self.size def insert(self, key, value): index = self.hash_function(key) for item in self.table[index]: if item[0] == key: item[1] = value # 更新值 return self.table[index].append([key, value]) # 插入新值 def search(self, key): index = self.hash_function(key) for item in self.table[index]: if item[0] == key: return item[1] return None # 测试 ht = HashTable(5) ht.insert(1, "one") ht.insert(6, "six") print(ht.search(1)) # 输出: one print(ht.search(6)) # 输出: six ``` --- ### 性能权衡 在选择哈希冲突解决方案时,需要综合考虑以下几个方面: - **时间 vs 空间**:开放寻址法通常占用较少的空间,但可能增加查找时间;链地址法则相反。 - **实现复杂度 vs 性能稳定性**:简单的方法如链地址法容易实现,但性能可能受负载因子影响较大;复杂方法如完美哈希则需要更多前期准备。 - **通用性 vs 特殊优化**:通用方法适用于大多数场景,而特殊优化方法(如完美哈希)仅适用于特定场景。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值