业界动态
【C++】哈希算法
2025-01-03 12:30

目录

【C++】哈希算法

 

1.哈希映射

1.1哈希的概念

 1.2哈希冲突

1.3哈希函数 

 1.31直接定值法

1.32除留余数法 

 2.解决哈希冲突

2.1闭散列法

2.11线性探测

2.12二次探测 

3代码实现 

3.1状态

3.2创建哈希节点类

3.21哈希表扩容

3.3数据插入

3.4查找与删除

 3.5仿函数 

完整cpp表

4.开散列哈希桶

4.1概念

4.2仿函数 

4.3哈希桶结点构建 

4.4哈希桶的查找和删除

4.5哈希桶的插入 


  • 在顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(logN),搜索的效率决于搜索过程中元素的比较次数。
  • 理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。 如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中

  1. 插入元素:根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
  2. 搜索元素:对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功

该方式即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数构造出来的结构称为哈希表(Hash Table)(或者称散列表)。
unordered_map和unordered_set的底层就是哈希来实现的,它是无序的。

  • Hash(key=key%capacity。

聪明的小伙伴已经找到矛盾了,如果说再添加一个数据5,那他存那? 

所以这种方式建立起来的映射就十分的不合理,所以我们要改进。

哈希设计的原则

  1. 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间。
  2. 哈希函数计算出来的地址能均匀分布在整个空间中。
  3. 哈希函数应比较简单。

 1.31直接定值法

取关键字的某个线性函数为散列地址:Hash(key)= A*key + B

 优点:简单,速度快,节省空间,查找key O(1)的时间复杂度

 缺点:当数据范围大时会浪费空间,不能处理浮点数,字符串数据

 使用场景:适用于整数,数据范围比较集中

1.32除留余数法 

把数据映射到有限的空间里面。设散列表中允许的地址数为m,取一个不大于m,但最接近或者等于m的质数p作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将key转换成哈希地址。

哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免哈希冲突。

看1.1节的例子

 解决哈希冲突最常用的方法是闭散列和开散列

闭散列也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。 

但是咋去找下一个空位置才是最关键的

2.11线性探测

线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止。

  • 插入通过哈希函数获取待插入元素在哈希表中的位置。如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。
  • 删除采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,否则会影响其他元素的搜索。比如删除元素1,如果直接删除掉,11查找起来可能会受影响。因此线性探测采用标记的伪删除法来删除一个元素。

给每个位置一个标记,用空、存在、删除3种状态来区分。

  1. 负载因子 = 存储的有效数据个数/空间的大小 。
  2. 负载因子越大,冲突的概率越高,增删查改效率越低。
  3. 负载因子越小,冲突的概率越低,增删查改的效率越高,但是空间利用率低,浪费多。 
  4. 负载因子 <1,就能保证发生哈希冲突时一定能找到空位置。

线性探测的优缺点:

  • 线性探测优点:实现非常简单。
  • 线性探测缺点一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低。

2.12二次探测 

二次探测改进了一些线性探测,但是也就那样,这里我就不给太多画面了。

所谓的二次探测我们可以理解为飞跃式,线性是一个一个找空位置,二次就是跳着找。

方法hash(key) + i^2

hash(11)=11%10+0*2=1,但是1的位置被占了,所以变成hash(11)+1*2,如果这个位置是空的就放进去,不是的话,i继续加。

状态:这里需要三种状态:空,已占用,已删除

如果只用有/没有来代表状态,那删除一个数据后,这个位置就是空的,那就不会再遍历了,但是它后面还有数据的话就存在问题了,所以我们用已删除这个状态来表示的话,还可以遍历后面的数据。

 
 
  
 
  • 查找:当数据是1时,直接映射到下标1处,此时该位置的状态是EXIST,数据是11时,映射到下标1处,但是已经有1了所以++往后找空位置找到后状态更新为EXIST。
  • 删除:删除1,找11,当删除1后,状态更新为DELETE,查找11时下标发现状态是DELETE时会继续往后移动,然后找到11.

3.21哈希表扩容

当插入的数据较多,而哈希表较短时,就要考虑到扩容,但是哈希的扩容不简单,因为一扩容,下标就变了,那很多数据的映射后的位置就变了。

现在的11在下标11处,就不在2处了。 

所以我们在上面提到了负载因子 填入表中的元素个数 / 散列表的长度

由于散列表的长度一定,所以负载因子和表中的元素个数成正比。元素个数越多,哈希冲突越大,所以我们一般将负载因子定在0.7~0.8,在代码中我们就定在0.7。

插入时我们再介绍。

插入的详细步骤

  • 去除重复
  1. 插入的值可能 已经存在,所以用用Find先进行查找
  2. 找到就返回false,没找到在插入。
  • 空间扩容
  1. 如果表是空的就给10个空间,否则扩大2倍。
  2. 建立一个新哈希表,把旧表的值插入到新表
  • 探测找位
  1. 如果位置的状态是EXIST,继续往后移
  2. 找到空位置后,插入,状态变为存在,_n++。
 
 

在1.0版本中我注意两个细节

  • hashi为啥要模size而不是capacity?

计算机开辟了20个空间,只存储10个数据,但是只能让计算机在前十个空间存,要不一旦用空的空间,遍历时遇到后就不在往后进行了。所以开的空间一般和数据个数一致。

  • while循环中为啥要加一步hashi%=_table.size()?

比如我们开了20个空间,下标16以后都存满了但是前面还有位置,但是我们存一个37,那他就一直找位置,直到找到19,然后就出这个哈希表了,所以要让他返回到表上再到下标靠前的位置去找。

接下来就要开辟空间了。但是这个可不简单。

当插入的数据较多,而哈希表较短时,就要考虑到扩容,但是哈希的扩容不简单,因为一扩容,下标就变了,那很多数据的映射后的位置就变了。

现在的11在下标11处,就不在2处了。 

所以我们在上面提到了负载因子 填入表中的元素个数 / 散列表的长度

由于散列表的长度一定,所以负载因子和表中的元素个数成正比。元素个数越多,哈希冲突越大,所以我们一般将负载因子定在0.7~0.8,在代码中我们就定在0.7。

在这里我们就要重新建一个哈希表。

 
 

如果哈希表中已经有插入的值时,我们就要去除冗杂,用find函数。

查找的大致思路和插入的很接近,这里就不在重复了。

 
 

删除时,我们直接把要删除的数据状态改成DELETE就行了,甚至内部的数据都不用删除。

这里的取模用的都是整数,那如果数据是浮点型?更甚至是字符串怎么搞?所依我们就要用到仿函数来进行类型转换。

 

完整cpp表

 

开散列也叫拉链法先对所有key用散列函数计算散列地址,把有相同地址的key每个key都作为一个桶,通过单链表链接在哈希表中

此时的表里面存储一个链表指针,就是把冲突的数据通过链表的形式挂起来。

它的算法公式hash(key)=key%capacity

 这里的插入可以是头插也可以是尾插,插入时是无序的。

 也就是说哈希桶的根本是一个指针数组,哈希桶的每一个位置存的都是一个链表指针。

这个指针数组里的每一个元素都是结点指针,并且头插的效率比较高。

这次我们先弄模板来将其他类型转换为size_t。

 

因为是指针数组,所以结点中的成员变量多了一个指向下一个桶的指针。

 

这里的查找删除操作和上面的如出一辙,但是哈希桶的存储是链表的形式,所以会和链表的相关操作很接近。

 
  • 去除重复
  1. 插入的值可能 已经存在,所以用用Find先进行查找
  2. 找到就返回false,没找到再进行插入。
  • 空间扩容
  1. 如果负载因子==1就进行扩容。
  2. 建立一个新哈希表,把旧表的值插入到新表。
  3. 再把新表交换到旧表那里。

但是在把旧表映射到新表时要释放掉旧表,vector类型会自动调用析构函数,然而存储的数据是Node*类型的,是内置类型,不会自动释放,结果就是哈希表释放了但是表中存的数据没释放,所以我们要手写一个析构函数。

  • 头插操作
  1. 根仿函数找到合适映射位置
  2. 进行头插操作并更新桶内数据个数。

所以我们首先写一个析构函数

 
 

整体代码

 
    以上就是本篇文章【【C++】哈希算法】的全部内容了,欢迎阅览 ! 文章地址:http://www78564.xrbh.cn/news/30808.html 
     文章      相关文章      动态      同类文章      热门文章      栏目首页      网站地图      返回首页 迅博思语移动站 http://www78564.xrbh.cn/mobile/ , 查看更多   
最新文章
颜霸邝玲玲,我的泰娱新老婆
看到邝玲玲的第一眼多数人就能明白为什么她可以在中国收获超高人气。中泰混血的她脸上没有泰系常见的钝感,五官流畅、大气、明艳
6G进入标准化元年 通信专家建议重点挖掘垂直行业需求
3月27日至31日,2025中关村论坛年会在京举行。期间,中国工程院院士张平,中关村泛联院院长、中国移动研究院院长黄宇红,中关村
三星手机重装系统攻略:五步轻松恢复出厂设置三星手机系统「三星手机重装系统攻略:五步轻松恢复出厂设置」
简介:在现代智能手机的使用中,重装系统或恢复出厂设置是解决许多软件问题的有效手段。对于三星手机用户,重装系统可以帮助清理
买全球吃全球 感受“舌尖上”的消博会
中国青年报客户端讯(中青校媒记者 田恒慧 中青报·中青网见习记者 戴瑶 记者 任明超)4月13日,第五届中国国际消费品博览会(简
仰天长叹,山东男篮0:2出局:落寞爆冷,克里斯成难掩的心痛短板
CBA季后赛12进8,首战失利的山东男篮,客场挑战北控男篮。对于山东男篮来说,球队已经没有任何的退路,如果再输,那么本赛季就提
新奥好彩免费资料大全-精选解释解析落实酷派5855手机「新奥好彩免费资料大全-精选解释解析落实」
新奥好彩是一款在中国颇受欢迎的彩票游戏,借助现代科技和大数据分析技术,为广大玩家提供了丰富的购彩体验与相应的策略指导。随
这家“药厂”,专治年轻人不开心
中国精酿啤酒市场,正经历舶来品的本土化蜕变。中国酒业协会数据显示,啤酒行业保持高增长,反弹性增长态势明显。《2024—2029年
理想星环OS开源,到底意味着什么?
3月27日,董事长兼CEO李想在2025中关村论坛年会上宣布开源理想汽车自研汽车操作系统——理想星环OS。上述消息在一定范围内引起热
春节送父母vivo Y200t:超高性价比手机只需873元,功能惊艳!vivo性价比最高的手机「春节送父母vivo Y200t:超高性价比手机只需873元,功能惊艳!」
春节来临,买新手机送父母成为不少子女的新年选择。在这个团圆的节日里,送一份陪伴和实用的礼物无疑能带来更多的幸福感。尤其是
爱高集团盘中最低价触及0.181港元,创近一年新低
截至4月16日收盘,(00328.HK)报0.192港元,较上个交易日下跌7.69%,当日盘中最低价触及0.181港元,创近一年新低。资金流向方面