0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于C++哈希表解决冲突

汽车玩家 来源:IT知识课堂 作者:IT知识课堂 2020-05-05 23:11 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

开放寻址是其中一种缓解散列冲突的编程技术,当使用开放寻址作为冲突解决技术时,键值对存储在表(数组)中,而不是像单独链表那样的数据结构中。这意味着我们需要时刻留意哈希表的尺寸以及当前表中已有的元素数量。因为一旦哈希表中有太多元素,也将很难找到可用的位置来存放我们新插入的元素,因此这里我们需要引入一个重要的术语,负载系数(Load Factor)

负载系数

基于C++哈希表解决冲突


其实就是表中已有元素个数和表尺寸的比例,我们要密切关注这个系数的是因为哈希表的O(1)恒定时间行为假设负载因子k保持一定的固定值,这意味着一旦k>阈值,我们就需要增加表的大小(理想情况下是指数增长,例如,两倍)

基于C++哈希表解决冲突


在上图中,你会看到有两种缓解冲突的方法,即单独链表和线性探测(Linear Probing),在开放寻址(线性探测)技术看来,一旦达到某个阀值,它的时间复杂度就会呈现指数级恶化的趋势

当我们想要将键值对插入哈希表时,我们对键进行哈希处理并获得该键值对所属位置的原始位置。如果我们的键被散列到的位置被占用(此时出现了冲突),对于开放寻址来说,同一个位置中不允许有两个键的,这不是数组的工作方式,我们要做的是使用一个探测序列函数(Probing Seque Function) 这里简称p(x),因为我们已从散列函数获取了冲突点的所在位置,现在我们使用p(x)进行探测直到在沿途发现一个空闲的位置为止

探测函数

您可以提出无限数量的探测序列,这里仅提及一些常见的探测函数:

线性探测(Linear Probing):p(x)= kx + b其中a,b是常数

二次探测(Quaratic Probing):p(x)= ax ^ 2 + bx + c,其中a,b,c是常数

双重散列(Double Hashing):p(k,x)= x * h(k),其中h(k)是辅助s散列函数

伪随机数发生器(Pseudo Random Number Generator): p(k,x)= x*RNG(h(k),x)其中RNG是以H(k)作为种子的随机数生成器函数

本篇仅介绍线性探测函数进行线性探测,因此给定输入参数x,当我们进行探测时,我们通常会将变量x初始化为0或1作为一个起点,如果我们找不到空闲的位置,会依次将x增加1,对以上所有这些探测函数都是一样的

开放寻址的通用算法

接下来,这是一个通用的开放寻址插入算法,假设我们有一个表的尺寸为n,开放寻址算法首先会初始化变量x=1,因为x是一个变量,我们要用它来探测,每当我们未能到达闲置的位置时,都需要递增x,然后我们通过散列函数获得keyHash,而实际上我们首先要查看表的索引,当表索引被占用意味着它不为空,那么新索引就是我们散列的最初位置(keyHash所指向的起始索引)加上探测函数的总和再于表尺寸N取模运算得到整数,由于我们总是回到表里,在循环中要递增x。下一次当我们在不同的位置探测时,在while循环中,最终我们会找到一个空闲的位置

x=1 keyHash=h(k) index=keyHash while table[index]!=NULL: index=(keyHash+p(k,x)) mod N x=x+1 insert(k,v,index)

死循环地狱(Chaos with Cycle)

由于我们知道负载系数被控制在一定的范围内,所以这里有个问题,就是开放寻址中的探测函数存在缺陷--死循环地狱,以表尺寸N为模的大多数随机选择的探测序列将产生比表大小N更短的循环。当您尝试插入一个键-值对并且循环中的所有存储桶都被占用时,这将成为灾难性问题,因为您将陷入无限循环,这在一些老外谈及哈希表的相关文章中有一个非常有趣的昵称叫死循环地狱(Chaos with Cycle)

为了生动说明什么叫死循环地狱,我们这里看一个例子,这里有一个尺寸为12的哈希表,并且使用开放寻址插入了一些键值对,该哈希表已经部分填充。 占用的单元格填充有键值对(Ki,Vi)和带有空令牌Φ的空单元格,如下图所示

基于C++哈希表解决冲突

假设我们使用探测序列函数p(x)=4x,并且在表中插入一个新的键值对,并且该键值对的散列值为8,即h(x)=8这意味着我们会在索引8的位置插入该键值对,但是该位置已被占用,因为这里已经有简直对(k5,v5),所以我们该怎么办呢?接下来,我们需要进行探测,所以我们计算: index=h(k)+p(1)=8+4 mod 12=0

此时,如下图,此时探测函数会跳转到索引为0的位置,糟糕的是索引1的位置也被占用了,因为(k1,v1)已经存在.

基于C++哈希表解决冲突

当x=2时,即index=h(k)+p(2)=(8+8) mod 12=4,探测函数会跳跃到索引4的位置,同样这里也是被占用的,如此类推

当x=3时,即index=h(k)+p(3)=(8+12) mod 12=8,p(x)跳跃到索引8的位置,该位置被占用

当x=4时,即index=h(k)+p(4)=(8+16) mod 12=0,p(x)跳跃到索引0的位置,该位置被占用

当x=5时,即index=h(k)+p(5)=(8+20) mod 12=4,p(x)跳跃到索引4的位置,该位置被占用
.....

这样尽管我们具有探测函数,但这种特定的情况下它一直在一个死循环里面一直做一些毫无意义的事情。

由这个例子我们可知探测函数存在缺陷,他们产生的周期短于表的尺寸,因此,我们要如何处理产生小于表大小的周期的探测功能?一般来说,一致的看法是我们不处理这个问题,相反,我们通过将探测函数的范围限制在那些产生长度为N的循环的函数上来避免这个问题,我们选择的那些产生的周期正好为N的探测函数,并且这些探测函数确实存在。

线性探测、二次探测和双重散列等技术都受到死循环地狱问题的影响,这就是为什么与这些方法一起使用的探测函数非常特殊的原因。这是一个很大的话题,将是接下来几篇文章会重点讲述这些,我们目前需要做的是重新定义非常具体的探测函数,这些函数会产生一个循环长度为表尺寸N,并且避免无法插入元素或陷入无限循环

注意,开放寻址对使用的哈希函数和探测函数非常敏感。如果使用单独的链接作为冲突解决方法,则不必担心此问题。

小结

我们本篇用一个反例生动地介绍了开放寻址插入算法的底层是由探测函数和散列函数相互作用的结果,同时我们也介绍了一些探测函数的固有缺陷,就是死循环地狱,下一篇我们会详细讨论线性探测函数的原理,敬请期待。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 存储
    +关注

    关注

    13

    文章

    4693

    浏览量

    89569
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66837
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C/C++条件编译

    条件编译是一种在编译时根据条件选择性地包含或排除部分代码的处理方法。在 C/C++ 中,条件编译使用预处理指令 #ifdef、#endif、#else 和 #elif 来实现。常用的条件编译指令有
    发表于 12-05 06:21

    C++程序异常的处理机制

    1、什么是异常处理? 有经验的朋友应该知道,在正常的CC++编程过程中难免会碰到程序不按照原本设计运行的情况。 最常见的有除法分母为零,数组越界,内存分配失效、打开相应文件失败等等。 一个程序
    发表于 12-02 07:12

    C/C++代码静态测试工具Perforce QAC 2025.3的新特性

     Perforce Validate 中 QAC 项目的相对/根路径的支持。C++ 分析也得到了增强,增加了用于检测 C++ 并发问题的新检查,并改进了实体名称和实
    的头像 发表于 10-13 18:11 345次阅读
    <b class='flag-5'>C</b>/<b class='flag-5'>C++</b>代码静态测试工具Perforce QAC 2025.3的新特性

    技能+1!如何在树莓派上使用C++控制GPIO?

    在使用树莓派时,你会发现Python和Scratch是许多任务(包括GPIO编程)中最常用的编程语言。但你知道吗,你也可以使用C++进行GPIO编程,而且这样做还有不少好处。借助WiringPi
    的头像 发表于 08-06 15:33 3573次阅读
    技能+1!如何在树莓派上使用<b class='flag-5'>C++</b>控制GPIO?

    C++ 与 Python:树莓派上哪种语言更优?

    Python是树莓派上的首选编程语言,我们的大部分教程都使用它。然而,C++在物联网项目中同样广受欢迎且功能强大。那么,在树莓派项目中选择哪种语言更合适呢?Python因其简洁性、丰富的库和资源而被
    的头像 发表于 07-24 15:32 642次阅读
    <b class='flag-5'>C++</b> 与 Python:树莓派上哪种语言更优?

    主流的 MCU 开发语言为什么是 C 而不是 C++

    在单片机的地界儿里,C语言稳坐中军帐,C++想分杯羹?难喽。咱电子工程师天天跟那针尖大的内存空间较劲,C++那些花里胡哨的玩意儿,在这儿真玩不转。先说内存这道坎儿。您当stm32f4的256kRAM
    的头像 发表于 05-21 10:33 778次阅读
    主流的 MCU 开发语言为什么是 <b class='flag-5'>C</b> 而不是 <b class='flag-5'>C++</b>?

    使用C++中的CyAPI编写的应用程序上遇到了问题,求解决

    我在使用 C++ 中的 CyAPI 编写的应用程序上遇到了问题。 我将 XferData() 方法与其他所有端点类型一起使用,没有遇到任何问题。 但是,将其与 Endpoint0 一起使用会引发
    发表于 05-13 06:11

    C++学到什么程度可以找工作?

    C++学到什么程度可以找工作?要使用C++找到工作,特别是作为软件开发人员或相关职位,通常需要掌握以下几个方面: 1. **语言基础**:你需要对C++的核心概念有扎实的理解,包括但不限于指针、内存
    发表于 03-13 10:19

    创建了用于OpenVINO™推理的自定义C++和Python代码,从C++代码中获得的结果与Python代码不同是为什么?

    创建了用于OpenVINO™推理的自定义 C++ 和 Python* 代码。 在两个推理过程中使用相同的图像和模型。 从 C++ 代码中获得的结果与 Python* 代码不同。
    发表于 03-06 06:22

    源代码加密、源代码防泄漏c/c++与git服务器开发环境

    源代码加密对于很多研发性单位来说是至关重要的,当然每家企业的业务需求不同所用的开发环境及开发语言也不尽相同,今天主要来讲一下c++及git开发环境的源代码防泄密保护方案。企业源代码泄密场景一、在很多
    的头像 发表于 02-12 15:26 876次阅读
    源代码加密、源代码防泄漏<b class='flag-5'>c</b>/<b class='flag-5'>c++</b>与git服务器开发环境

    基于OpenHarmony标准系统的C++公共基础类库案例:ThreadPoll

    1、程序简介该程序是基于OpenHarmony标准系统的C++公共基础类库的线程池处理:ThreadPoll。本案例完成如下工作:创建1个线程池,设置该线程池内部有1024个线程空间。启动5个线程
    的头像 发表于 02-10 18:09 599次阅读
    基于OpenHarmony标准系统的<b class='flag-5'>C++</b>公共基础类库案例:ThreadPoll

    Spire.XLS for C++组件说明

    Spire.XLS for C++ 是一款专业的 C++ Excel 组件,可以用在各种 C++ 框架和应用程序中。Spire.XLS for C++ 提供了一个对象模型 Excel
    的头像 发表于 01-14 09:40 1297次阅读
    Spire.XLS for <b class='flag-5'>C++</b>组件说明

    EE-112:模拟C++中的类实现

    电子发烧友网站提供《EE-112:模拟C++中的类实现.pdf》资料免费下载
    发表于 01-03 15:15 0次下载
    EE-112:模拟<b class='flag-5'>C++</b>中的类实现

    AKI跨语言调用库神助攻C/C++代码迁移至HarmonyOS NEXT

    /C++代码快速迁移至HarmonyOS NEXT。凭借卓越的兼容性,AKI已成为厂商与开发者打造鸿蒙原生应用过程中广泛使用的跨语言调用解决方案。 AKI是一款专为鸿蒙原生开发设计的FFI(外部函数接口
    发表于 01-02 17:08

    运动控制卡周期上报实时数据IO状态之C++

    使用C++进行运动控制卡的周期上报功能实现
    的头像 发表于 12-17 13:59 1528次阅读
    运动控制卡周期上报实时数据IO状态之<b class='flag-5'>C++</b>篇