当前位置: 首页 > news >正文

【数据结构】哈希表

📝前言:
这篇文章我们来讲讲unorder_mapunordered_set,以及他们底层实现的数据结构哈希表,最后再模拟封装实现一下:
1,unordered_mapunordered_set的介绍
2,哈希表的介绍
3,封装unordered_mapunordered_set

🎬个人简介:努力学习ing
📋个人专栏:C++学习笔记
🎀CSDN主页 愚润求学
🌄其他专栏:C语言入门基础,python入门基础,python刷题专栏,Linux


文章目录

  • 一,unordered_map && unordered_set
    • 和map和set的对比
  • 二,哈希表
    • 哈希表的映射
    • 负载因子
    • 哈希冲突
    • 哈希函数
      • 除法散列法 / 除留余数法(重点)
        • M 的取值的讲究
      • 乘法散列法
      • 全域散列法
    • 哈希冲突的解决方法
      • 开放定址法
        • 线性探测
        • 二次探测
        • 双重散列
      • 链地址法
    • key转换成整型
  • 三,封装实现

一,unordered_map && unordered_set

和map和set的对比

  • 迭代器区别unordered的意思就是无序的,即:unordered_map的迭代器遍历出的序列是无序的。(注意两个都是单向迭代器)
  • 底层区别map的底层是红黑树,而unordered_map的底层是哈希表,增删改查的效率上:红黑树:O(logN),哈希表:O(1)
  • 对key的要求不同map的key要求支持 < 比较,而unordered_map的key要支持 转成整型(这个和哈希表特性有关) 和 ==比较
  • 接口区别:大部分接口和map相同,多了一些用于哈希表的特有接口
  • 同样有multi版本(即:允许重复key)

在这里插入图片描述

  • 仿函数Hash:把key转换成整型
  • 仿函数Pred:让key支持==比较

二,哈希表

哈希表:将数据通过特定的哈希函数映射到存储位置上,后续查找数据的时候,可以利用哈希函数直接算出对应的位置,查找效率变成:O(1)

哈希表的映射

哈希表的映射就是指数据通过哈希函数映射到特定位置的过程。

比如直接定址法
以关键字key作为哈希地址,或者通过一个简单的线性函数计算出哈希地址。比如,把字母映射到数组下标:arr[ch - 'a']

  • 优点是简单直接,不会产生冲突
  • 空间地址要开很大,且容易浪费

负载因子

假如哈希表已经映射存储了N个值,哈希表的大小为M,则负载因子(load factor) = N / M N/M N/M

哈希冲突

指:有两个不同key映射到同一个位置。哈希冲突不可避免(除非直接定址),但是要尽量减少

哈希函数

key映射转换成哈希地址的函数。哈希函数的目的就是进行映射,一个能减少冲突的函数才是好函数。

除法散列法 / 除留余数法(重点)

除法散列法也叫除留余数法,即:
假设哈希表的大小为M,那么通过key除以M余数作为映射位置的下标,即:哈希函数为:h(key) = key % M(这样我们就可以得到一个 [0, M-1] 的数)

M 的取值的讲究

先上结论再解释:

  • M的取值要避免取,2的n次幂 / 10的n次幂这种值。
  • 应该取:不太接近2的整数次幂的⼀个质数

原因:质数的因数只有 1 和它本身,这意味着:作为除数时,出现相同结果,出现冲突的概率更低。
但是,当哈希表扩容时,因为我要保证M一直为素数所以无法按标准的2倍来扩容,为此:sgi版本的哈希表给了⼀个近似2倍的质数表,每次去质数表获取扩容后的大小:

inline unsigned long __stl_next_prime(unsigned long n)
{// Note: assumes long is at least 32 bits.static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;
}

当然这不是绝对的,如果有其他方法能够减少冲突也是可行的。

比如,Java中的HashMap采取除法散列法时就是2的整数次幂做哈希表的大小M,但是它的哈希函数的处理不止仅取模这么简单。
假如它的M取的值是:2^16:它会让key的二进制中的后十六位异或前十六位,得到最后的映射地址
即:

  1. key & (1 << 16 - 1),得到后16位key1
  2. key >> 16,得到前16位key2
  3. 让 1 和 2 的结果想异或,得到映射地址

这样的做法可以让所有位都参与运算,得到的地址值重复的概率更低

乘法散列法

乘法散列法对哈希表大小M没有要求。
我们想想哈希函数本质是什么?把Key映射到 [0, M - 1] 里,即 M * [0, 1)得到的数一定满足要求。那只要找到一个和 Key有关的 [0, 1) 的小数就可以了。
所以,乘法散列法的步骤:

  • 第一步:Key 乘以 A(0 < A < 1),取得到的数的小数部分 B
  • 第二步:M * B 向下取整,得到映射地址

全域散列法

Hab (key) = ((a × key + b)%P )%M,P选⼀个足够大的质数,a可以随机选 [1,P-1] 之间的任意整数,b可以随机选 [0,P-1] 之间的任意整数,这些函数构成了⼀个有P*(P-1)个函数的全域散列函数组。假设 P = 17,M = 6,a = 3,b = 4,则 :H34 (Key) = ((3 × Key + 4) % 17) % 6
每次程序启动的时候,会随机选取一个a和b生成对应的哈希函数。
这种随机性为的是:避免有心之人针对我们提供的哈希函数,特意构造出⼀个发⽣严重冲突的数据集,导致我们的程序卡死。

哈希冲突的解决方法

开放定址法

在开放定址法中所有的元素都放到哈希表里,当⼀个关键字key用哈希函数计算出的位置冲突了,则按照某种规则找到⼀个没有存储数据的位置进行存储,开放定址法中负载因子⼀定是小于 1的。

线性探测

从发生冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置。
比如: M = 17,key1 = 8, key2 = 25
key1 放入到下标为 8 的位置,25 放入的时候,发现位置 8 有数据了,则向后找位置,发现下标为 9 的位置没有数据,则放入位置 9

查找的时候,算出对应的哈希地址,如果发现有值则已知2往后找,直到遇到空位置则代表没找到停止。
但是,思考一下:如果中途有一个元素被删除了,那不是提前遇到空了吗
所以我们每一个位置需要设置三种状态:空,删除,存在。
当遇到删除状态的时候,还继续往后找。

缺点:容易造成元素的扎堆

二次探测

从发⽣冲突的位置开始,依次左右按⼆次方跳跃式探测,直到寻找到下⼀个没有存储数据的位置为止。

在这里插入图片描述

双重散列

第⼀个哈希函数计算出的值发⽣冲突,使用第⼆个哈希函数计算出⼀个跟key相关的偏移量值,不断往后探测,直到寻找到下⼀个没有存储数据的位置为止。

链地址法

开放定址法中各种方法,不管怎样都还是在内部互相抢占位置。
链地址法中所有的数据不再直接存储在哈希表中,哈希表变成一个指针数组,当没有数据映射到对应位置时,这个位置的指针为空,有多个数据映射到这个位置时,我们把这些冲突的数据链接成⼀个链表,挂在哈希表这个位置下面,链地址法也叫做拉链法或者哈希桶。

链地址法的负载因子可以大于 1。负载因子越⼤,哈希冲突的概率越⾼,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低

扩容:
stl中unordered_xxx的最⼤负载因子基本控制在1,大于1就扩容。

当一个桶特别长影响查找效率:Java8的HashMap中当桶的⻓度超过⼀定阀值(8)时就把链表转换成红黑树。

key转换成整型

因为key有可能是string或其他类型,但是我们的哈希函数多数时候是基于整型计算的,所以要key要能够转换成整型

三,封装实现

代码有点长,可以上我的GIthub仓库获取


🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!

http://www.xdnf.cn/news/115147.html

相关文章:

  • 使用深度 Q 学习解决Lunar lander问题
  • Linux424 chage密码信息 gpasswd 附属组
  • 数据库数据删除与修改实验
  • Flink 消费 Kafka 数据流的最佳实践
  • 4.ArkUI TextInput的介绍和使用
  • 多语言虚拟币海外游戏娱乐平台源码详解(整合篇)
  • 上岸率85%+,25西电先进材料与纳米科技学院(考研录取情况)
  • 【投屏软件】手机投屏软件
  • 省时省力的AI批量原创SEO文章生成工具解放双手
  • CentOS 7 基于 Nginx 的 HTML 部署全流程指南
  • 18.应用聚合、指标显示、应用状态,从Heimdall说起(二)
  • 十分钟恢复服务器攻击——群联AI云防护系统实战
  • 【专题刷题】二分查找(一):深度解刨二分思想和二分模板
  • PostgreSQL 数据库备份与恢复全面指南20250424
  • 反爬系列 IP 限制与频率封禁应对指南
  • DBdriver使用taos数据库
  • 神经网络基础[ANN网络的搭建]
  • 【晶振】晶振的工作原理及其与单片机关系
  • CGAL 网格内部生成随机点
  • 扩展中国剩余定理
  • 高企复审奖补!2025年合肥市高新技术企业重新认定奖励补贴政策及申报条件
  • 项目右键没有add as maven project选项
  • 栈(Stack)和队列(Queue)
  • 华为手机怎么进行音频降噪?音频降噪技巧分享:提升听觉体验
  • 【前端】【业务场景】【面试】在前端开发中,如何实现一个可拖动和可缩放的元素,并且处理好边界限制和性能优化?
  • PS Mac Photoshop 2025 for Mac图像处理 PS 2025安装笔记
  • SQL Server 2008 R2中varchar(max)的含义
  • 如何获取静态IP地址?完整教程
  • ESP32上C语言实现JSON对象的创建和解析
  • 亚马逊英国站FBA费用重构:轻小商品迎红利期,跨境卖家如何抢占先机?