C++----哈希表

1. unordered_set和set的使用差异

  1. set要求Key支持小于比较,而unordered_set要求Key支持转成整形且支持等于比较

  2. set的iterator是双向迭代器,unordered_set是单向迭代器,其次set底层是红黑树
    红黑树是二叉搜索树,走中序遍历是有序的,所以set迭代器遍历是有序+去重
    而unordered_set底层是哈希表,迭代器遍历是无序+去重

  3. 大多数场景下,unordered_set的增删查改更快⼀些,因为红黑树增删查改效率是logn ,而哈希表增删查平均效率是O(1) ,具体可以参看下⾯代码的演示的对比差异。
    map和unordered_map的差异也类似

2. 直接定址法

当关键字的范围比较集中时,直接定址法就是非常简单高效的方法
比如⼀组关键字值都在[a,z]的小写字母,那么我们开⼀个26个数的数组,每个关键字acsii码-a的ascii码就是存储位置的下标

字符串中的第一个唯一字符

c 复制代码
class Solution {
public:
 int firstUniqChar(string s) {
 // 每个字⺟的ascii码-'a'的ascii码作为下标映射到count数组,数组中存储出现的次数 
 int count[26] = {0};
 
 // 统计次数 
 for(auto ch : s)
 {
 count[ch-'a']++;
 }
 for(size_t i = 0; i < s.size(); ++i)
 {
 if(count[s[i]-'a'] == 1)
 return i;
 }
 return -1;
 }
};

3. 哈希冲突

直接定址法的缺点也非常明显,当关键字的范围比较分散时,就很浪费内存甚至内存不够用。假设只要数据范围是[0,9999]的100(N)个值,此时大量浪费空间

所以一般我们采用哈希函数来解决,除模运算,用每个值%100(N),得到的就是这个数的下标,但50%100=50,250%100=50,此时两个不同的key映射到同一个位置,这种问题我们叫做哈希冲突,或者哈希碰撞,冲突是不可避免的,所以尽可能设计出优秀的哈希函数

4. 负载因子

哈希表中已经映射存储了N个值,哈希表的大小为M,负载因子=N/M,他的英文为load factor。负载因子越大,哈希冲突的概率越高,空间利用率越高;负载因子越小,哈希冲突的概率越低,空间利用率越低

5. 哈希函数

⼀个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到,但是我们要尽量往这个方向去考量设计

5.1 除法散列法/除留余数法

除法散列法也叫做除留余数法,顾名思义,假设哈希表的大小为M,那么通过key除以M的余数作为映射位置的下标,也就是哈希函数为:h(key)=key%M

当使用除法散列法时,要尽量避免M为某些值,如2的幂,10的幂等。如果是2的x次幂 ,那么key%2的x次幂本质相当于保留key的后X位,那么后x位相同的值,计算出的哈希值都是⼀样的,就冲突了

{63,31}看起来没有关联的值,如果M是16,也就是2的4次幂 ,那么计算出的哈希值都是15,因为63的二进制后8位是00111111,31的二进制后8位是00011111,因为后四位相同,如果是10的x次幂 ,就更明显了,保留的都是10进值的后x位,如:{112,12312},如果M是100,也就是10的2次幂 ,那么计算出的哈希值都是12冲突

当使用除法散列法时,一般 M取不太接近2的整数次幂的⼀个质数(素数)

6. 处理哈希冲突

实践中哈希表⼀般还是选择除法散列法作为哈希函数,当然哈希表无论选择什么哈希函数也避免不了冲突,那么插入数据时,如何解决冲突呢?主要有两种两种方法,开放定址法和链地址法。

6.1 开放定址法

1. 线性探测

从发生冲突的位置开始,依次线性向后探测,直到寻找到下⼀个没有存储数据的位置为止,如果走到哈希表尾,则回绕到哈希表头的位置, 遍历到空查找结束

下面演示 {19,30,5,36,13,20,21,12} 等这⼀组值映射到M=11的表中。

h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) = 10,h(12) = 1

这里首先是19%M=8,映射到8的位置,30%M=8也应该映射到8的位置,但是8的位置已经有值了,只能把30放到9的位置,20%11=9,但此时9的位置已经有值了,只能放到10的位置,21%11=10,10的位置有值了,放到0的位置,10+1%M=0

存在互相践踏的问题导致效率低下

2. 二次探测

二次探测是映射到的下标位置±i的平方,i从1开始取值,i的最大取值是表的一半,第一次一般是+,第二次是-,当计算的值小于0时+M
h(19) = 8, h(30) = 8, h(52) = 8, h(63) = 8, h(11) = 0, h(22) = 0

6.2 链地址法

h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,h(13) = 2,h(20) = 9,h(21) = 10,h(12) = 1,h(24) = 2,h(96) = 88


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

相关推荐
三月微暖寻春笋2 小时前
【和春笋一起学C++】(六十一)公有继承中的多态
c++·多态·virtual·基类·虚函数·公有继承
兩尛3 小时前
409. 最长回文串
c++·算法·leetcode
智者知已应修善业3 小时前
【pta反转加法构造回文数c语言1000位】2025-1-31
c语言·c++·经验分享·笔记·算法
汉克老师3 小时前
GESP2024年3月认证C++二级( 第三部分编程题(2)小杨的日字矩阵 )
c++·矩阵·循环结构·gesp二级·gesp2级·打印图形
yyjtx3 小时前
DHU上机打卡D27
c++·算法·图论
白太岁4 小时前
C++:(5) 单例模式与支持初始化失败的单例模式
c++·单例模式
hwtwhy4 小时前
【情人节特辑】C 语言实现浪漫心形粒子动画(EasyX 图形库)
c语言·开发语言·c++·学习·算法
日月云棠4 小时前
UE5 打包后 EXE 程序单实例的两种实现方法
前端·c++
XiaoHu02074 小时前
MySQL基础(第一弹)
数据库·c++·mysql