hash table碰撞处理

本文介绍了两种解决哈希表碰撞的策略:开放寻址(open addressing)和独立链表(separate chaining)。开放寻址包括线性探测(linear probing)、二次探测(quadratic probing)和双重散列(double hashing),每种方法都有其特点和应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有两种策略来解决hash table的碰撞问题。第一种策略是open addressing,如果数组中当前位置已经被占用,他会为当前数据重新选择一个位置;第二种策略是separate chaining,在数组每一个位置安放一个链表。

1.Open Addressing

如果当前数据所修位置被占用,该策略会为数据重新选择一个位置。这种测略有三种方法可以使用:

a.Liner probing

这种方法简单的以当前位置为起点,线性搜索空闲位置。如果hash函数选择了第n个位置给当前数据,但是n位置已经被占用,该方法将会尝试n+1,n+2.........,知道找到一个空闲位置,如果到达数组末尾,则从数组头继续搜索。这个尝试不同位置的过程叫做probing。

之所以叫做Liner probing,是因为这个过程看起来像线性搜索。

当需要在hash table中搜索数据时候,同样要是用probing,当通过key来搜索数据,且hash函数得到的位置是x,如果x位置包含其他的数据,那么继续搜索x+1,x+2,直到搜索到目标元素,或者x+k位置为空,或者又重新便利到x位置。

Liner probing的问题是clustering(聚合),数据元素成块状分布。如果插入一个映射到x位置的值,但是x被占用了,插入方法将尝试x+1。如果下一个元素被映射到x+1,而这个位置被占用了,那他就得继续往后尝试。数据将会聚集成一块一块的。在实际应用中,如果插入数据相似,那么会产生数据聚合,导致搜索和插入数据效率下降。

解决这个问题的方法是move ahead,不仅仅是前移一个元素位置。在quadratic probing中,插入方法将尝试x+1^2,x+2^2,x+3^2.........直到找到空闲位置,在查找时候也要这么做。


b.Quadratic probing

Quadratic probing的好处是可以降低clustering,因为probing的偏移是n^2,而不是1,不会使得数据很接近。但是如果很多数据同时被映射到同一个位置上,那么这种方法也是于事无补的。当有很多数据被映射到同一个位置时候,他将会尝试x+1,x+4,x+9...............这将使数据查找变得困难。


c.double hashing

double hashing中,probing的偏移取决于key value自己。当key映射到位置x,而x已经被占用的时候,则用第二个hash函数对key进行处理得到y,尝试x+y,x+2y,x

+3y..........直到找到一个可以插入的位置。选择第二个hash函数的目标是:hash函数值永远大于等于1,对于key的得到的hash结果和第一个hash函数不同。通常,第二个hash函数这么写:

probe offset  =  c - key%c,    c是一个小于数组大小的常数


2.Separate chaining

这种方法在数组的每一个位置上用一个链表

### 如何优化哈希表性能 #### 选择合适的哈希函数 为了提高哈希表的效率,选择一个好的哈希函数至关重要。理想的哈希函数应该能够均匀分布键值,从而减少冲突的发生率。常见的哈希算法有MD5、SHA系列等加密哈希函数,但在实际应用中更常用的是非加密哈希函数如FNV-1a、MurmurHash等[^1]。 #### 控制负载因子 负载因子是指哈希表中的元素数量与数组大小的比例。保持较低的负载因子有助于降低冲突的概率并提升查找速度。通常建议将负载因子控制在0.7左右,在达到这个阈值时自动扩展哈希表容量以维持良好的性能表现[^3]。 #### 改善冲突解决方案 针对不可避免存在的冲突情况,可以通过改进冲突解决策略来增强哈希表的整体效能: - **二次探测法**:相比于简单的线性探测方式,采用平方级增量序列(例如`hc(key,i)=(hash0+i*i)%M`)可以在一定程度上缓解因连续碰撞造成的聚集效应,使得新加入的数据更容易找到合适的位置存放[^5]。 - **双重散列(Double Hashing)**:此方法利用第二个辅助性的哈希函数计算步长来进行跳跃式的探查过程,进一步减少了特定模式下的局部密集度问题,提高了空间利用率和访问效率[^4]。 - **链地址法(Chaining with Linked Lists or Trees)**:对于频繁发生冲突的情形,考虑使用链接列表或者其他自平衡树形结构代替单一数组单元保存多个具有相同索引的关键字项,虽然这会增加额外的空间开销,但却能显著加快检索操作的速度。 ```cpp // C++示例代码展示如何定义一个带有双散列机制的手动管理内存版本哈希集合类模板 template<typename T> class HashSet { private: static const int TABLE_SIZE = 997; // Prime number as table size reduces collisions std::vector<std::unique_ptr<Node<T>>> buckets; public: bool insert(const T& value); }; bool HashSet<T>::insert(const T &value){ unsigned long index = hash(value); // Primary hashing function result if (!buckets[index]) { buckets[index].reset(new Node<T>(value)); return true; } else { unsigned long stepSize = secondary_hash(value), probeIndex = index; do{ probeIndex = (probeIndex + stepSize) % TABLE_SIZE; if(!buckets[probeIndex]){ buckets[probeIndex].reset(new Node<T>(value)); return true; } }while(probeIndex != index); throw std::runtime_error("Table full"); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值