C++ unordered_map和unordered_set的使用,哈希表的实现
文章目录
- unordered_map,unorder_set和map ,set的差异
- 哈希表的实现
- 概念
- 直接定址法
- 哈希冲突
- 哈希冲突举个例子
- 负载因子
- 将关键字转为整数
- 哈希函数
- 除法散列法/除留余数法
- 哈希冲突的解决方法
- 开放定址法
- 线性探测
- 二次探测
- 开放定址法代码实现
- 哈希表的代码
unordered_map,unorder_set和map ,set的差异
unordered_map,unordered_set在功能方面和map,set基本一致
unordered_map和unordered_set底层是哈希表,遍历无序,查找删除修改的效率为O(1)
map和set底层是红黑树,遍历有序,查找删除修改的效率为O(logN)
- 功能:迭代器之间也有差异,set是双向迭代器,unordered_set是单向迭代器,set底层是红黑树,走中序是有序的,所以迭代器的遍历是有序+去重,unordered_set底层是哈希表,迭代器遍历是无序+去重
- 效率:unordered_set和set性能方面也有差异,set是O(logN),unordered_set是O(1)
- 使用要求:对key的要求不同,set要求key支持小于的比较,unordered_set要求key转为整形且要支持等于的比较
- unordered_set,unordered_map和map,set要求基本一致
- unordered_multimap和unordered_multiset支持键值冗余(key冗余)
- 效率上无序的比有序的总体上要好很多,但是在排有序的数的时候,有序的删除,插入比无序的效率高,但是查找效率还是无序的效率高
void set_test1()
{// 迭代器遍历set<int> s = { 3,1,6,7,8,2};set<int>::iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}// 3 1 6 7 8 2cout << endl;
}int main()
{set_test1();return 0;
}
哈希表的实现
概念
- 哈希表也叫散列表,散列有散乱排列的意思。通过关键字Key跟存储位置建立一个映射关系
直接定址法
- 适合关键字的范围比较集中的,直接定址法是非常高效的方法。比如给’a’ - 'z’的小写字母为范围,通过直接映射或相对映射,关键字的值就是存储位置的下标,下面也有一道题是这样的。只适合整形,字符串,浮点数都不行
字符串中的第一个唯一字符
class Solution
{
public:int firstUniqChar(string s) {int hash[26] = {0};for(auto ch : s){hash[ch - 'a']++;} for(int i = 0;i < s.size();i++){if(hash[s[i] - 'a'] == 1)return i;}return -1;}
};
哈希冲突
- 直接定址法的缺点非常明显,当关键字比较分散时,会浪费空间甚至空间会不够用。当有N个值要映射到M个空间中(M >= N),就要借助哈希函数,关键字key要放到数组的h(key)的位置,h(key)计算的值要在[0,M)中。
- 哈希冲突也叫哈希碰撞,就是两个不同的key映射到同一个位置了,我们要设计一个哈希函数来减少哈希冲突,在实际问题中哈希冲突是不可避免的
哈希冲突举个例子
N == 100 M == 200,N个数要存到M个空间中
除法散列法:h(key) = key % M
哈希冲突:3 % 200 = 3,203 % 200 = 3
负载因子
哈希表中已经存了N个值,哈希表的大小为M,负载因子 = N/M,负载因子也叫装载因子/载荷因子,负载因子越大,哈希冲突越高,空间利用率越高,相反就越低。
将关键字转为整数
比如是浮点数转为整数或者有符号的整数(负数)要转为正数,字符串转为整数。
哈希函数
一个好的哈希函数应该让N个关键字被等概率的均匀的散列分布到哈希表的M个空间中,但是实际中却很难做到,但是我们要尽量往这个方向去考量设计。
除法散列法/除留余数法
- 除法散列表是最常用的,哈希表的大小为M,通过key除以M的余数作为映射的下标,h(key) = key % M
- 使用这个方法时要注意M不要为2的幂,10的幂等。如果是2^X,那么key%2 ^ X,相当于保留key的后X位,那么key的后X位相同的值就哈希冲突了。
- 比如63,31,如果M是16,2 ^ 4,计算出的哈希值都是15,00111111为63,00011111为31,后4位都是1111,所以会哈希冲突。10进制的,比如123和345123,如果M = 10 ^ 3,保留后三位,都是123,也哈希冲突。
- 当使用除留余数法时,建议M取不太接近2的整数次幂的一个质数
- %其实用到了除,除的效率比位运算的效率更低,所以Java中用了位运算
Java中用了2的整数次幂作为哈希表的大小M
哈希冲突的解决方法
开放定址法
- 开放定址法,哈希表是空的,把所有元素放到哈希表中,当关键字key用哈希函数找到了冲突位置,就按照某种规则去找一个没有存储数据的位置进行储存。这里有三种规则:线性探测,二次探测,双重探测
线性探测
现在给一组数据:
{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
上面这组数据在8位置就冲突了
- 线性探测:从发生冲突的位置依次往后探测,直到找到一个没有存储数据的位置为止,如果走到哈希尾都没有找到,则回绕到哈希头继续往后找
- h(key) = hash0 = key % M , hash0位置冲突了,则线性探测公式为:hc(key, i) = hashi = (hash0 + i) % M, i = {1, 2, 3, …, M − 1},因为负载因子必须小于1(因为要留一个位置不放数据,不然会出问题),则最多探测M-1次,一定能找到一个存储key的位置。
- 连续冲突:hash0位置连续冲突,(你占我的位置,我占你的位置)这种现象叫做 群集/堆积,二次探测可以改善这样的问题
二次探测
二次探测和线性探测非常类似
如果往左走回绕到哈希表尾,用减,比如3-9,到5的位置停下,-6 + M,M == 11, -6 + M = 5
// 二次探测
int flag = 1;
while (_tables[hashi]._state == EXIST)
{// 存在进行二次探测,删除和空就退出hashi = (hash0 + i*i*flag) % _tables.size();if (hashi < 0)hashi += _tables.size();if (flag == 1){flag = -1;}else{flag = 1;++i;}
}
开放定址法代码实现
h(19) = 8,h(30) = 8,h(5) = 5,h(36) = 3,
h(13) = 2,h(20) = 9,h(21) =10,h(12) = 1
蓝色的四个位置连续冲突
查找的原则:遇到删除,存在才继续找,遇到空就结束查找
状态标识:存在,空和删除,空和删除可以放值,空就线性探测
要注意给每个存储值的位置加一个状态标识,否则删除一些值以后,会影响后面冲突的值的查找。
如下图,我们删除30,会导致查找20失败,当我们给每个位置加⼀个状态标识{EXIST,EMPTY,DELETE} ,删除30就可以不用删除值,而是把状态改为 DELETE ,那么查找20时是遇到 EMPTY 才能停止,就可以找到20。
哈希表的代码
#pragma once#include<vector>// 枚举状态
enum State
{EXIST,EMPTY,DELETE
};template<class K,class V>
struct HashData
{pair<K, V> _kv;State _state = EMPTY;
};template<class K,class V>
class HashTable
{
public:// 构造HashTable()// 会取到53:_tables(__stl_next_prime(0)),// 11是数据个数()_n(0){}// 为了解决M是质数的问题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);// lower_bound取表中大于等于first的数return pos == last ? *(last - 1) : *pos;}// 插入bool Insert(const pair<K, V>& kv){// 不支持冗余if (Find(kv.first)){return false;}// 负载因子大于等于0.7就扩容if (_n * 10 / _tables.size() >= 7){// 扩容//vector<HashTable<K, V>> newtables(_tables.size()*2);//for (auto& data : _tables)//{// // 把旧表的数据映射到新表// // 不能直接拷贝,因为映射关系还是原来的(会乱)// if (data._state == EMPTY)// {// size_t hash0 = data._kv.first % newtables.size();// // ...// }//}//_tables.swap(newtables);// 上面的方法代码过于冗余// 把新表和旧表交换HashTable<K, V> newht;// newht._tables.resize(_table.size() * 2);newht._tables.resize(__stl_next_prime(_table.size()));for (auto& data : _tables){// 把旧表的数据映射到新表if (data._state == EMPTY){// 相当于递归newht.Insert(data._kv);}}// 函数结束,newht销毁,数据交换给旧表_tables.swap(newht._tables);}// key / M , M哈希表的空间大小size_t hash0 = kv.first % _tables.size();size_t hashi = hash0;size_t i = 1;while (_tables[hashi]._state == EXIST){// 存在进行线性探测,删除和空就退出hashi = (hash0 + i) % _tables.size();++i;}// 二次探测//int flag = 1;//while (_tables[hashi]._state == EXIST)//{// // 存在进行二次探测,删除和空就退出// hashi = (hash0 + i*i*flag) % _tables.size();// if (hashi < 0)// hashi += _tables.size();// if (flag == 1)// {// flag = -1;// }// else// {// flag = 1;// ++i;// }//}_tables[hashi]._kv = kv;_tables[hashi]._state = EXIST;++_n;return true;}// 查找HashData<K, V>* Find(const K& key){size_t hash0 = key % _tables.size();size_t hashi = hash0;size_t i = 1;// Find等于空就找不到了while (_tables[hashi]._state != EMPTY){if (_tables[hashi]._state == EXIST&& _tables[hashi]._kv.first == key){return &_tables[hashi];}// 存在进行线性探测,删除和空就退出hashi = (hash0 + i) % _tables.size();++i;}return nullptr;}// 删除bool Erase(const K& key){HashData<K, V>* ret = Find(key);if (key){ret->_state = DELETE;return true;}else{return false;}}private:vector<HashData<K, V>> _tables;size_t _n;// 记录哈希表中的数据个数
};#include"HashTable.h"int main()
{int a[] = { 19,30,5,36,13,20,21,12 };HashTable<int, int> ha;for (auto& e : a){ha.Insert({ e,e });}ha.Erase(20);if (ha.Find(30)){cout << "找到了" << endl;}if (ha.Find(20)){cout << "找到了" << endl;}else{cout << "没有找到" << endl;}return 0;
}
相关文章:

C++ unordered_map和unordered_set的使用,哈希表的实现
文章目录 unordered_map,unorder_set和map ,set的差异哈希表的实现概念直接定址法哈希冲突哈希冲突举个例子 负载因子将关键字转为整数哈希函数除法散列法/除留余数法 哈希冲突的解决方法开放定址法线性探测二次探测 开放定址法代码实现 哈希表的代码 un…...

games101-作业3
由于此次试验需要加载模型,涉及到本地环节,如果是windows系统,需要对main函数中的路径稍作改变: 这么写需要: #include "windows.h" 该段代码: #include "windows.h" int main(int ar…...

【Block总结】高效多尺度注意力EMA,超越SE、CBAM、SA、CA等注意力|即插即用
论文信息 标题: Efficient Multi-Scale Attention Module with Cross-Spatial Learning 作者: Daliang Ouyang, Su He, Guozhong Zhang, Mingzhu Luo, Huaiyong Guo, Jian Zhan, Zhijie Huang 论文链接: https://arxiv.org/pdf/2305.13563v2 GitHub链接: https://github.co…...
Pwn 入门核心工具和命令大全
一、调试工具(GDB 及其插件) GDB 启动调试:gdb ./binary 运行程序:run 或 r 设置断点:break *0x地址 或 b 函数名 查看寄存器:info registers 查看内存:x/10wx 0x地址 (查看 10 个 …...

探索AI(chatgpt、文心一言、kimi等)提示词的奥秘
大家好,我是老六哥,我正在共享使用AI提高工作效率的技巧。欢迎关注我,共同提高使用AI的技能,让AI成功你的个人助理。 "AI提示词究竟是什么?" 这是许多初学者在接触AI时的共同疑问。 "我阅读了大量关于…...

利用飞书机器人进行 - ArXiv自动化检索推荐
相关作者的Github仓库 ArXivToday-Lark 使用教程 Step1 新建机器人 根据飞书官方机器人使用手册,新建自定义机器人,并记录好webhook地址,后续将在配置文件中更新该地址。 可以先完成到后续步骤之前,后续的步骤与安全相关&…...
小白爬虫冒险之反“反爬”:无限debugger、禁用开发者工具、干扰控制台...(持续更新)
背景浅谈 小白踏足JS逆向领域也有一年了,对于逆向这个需求呢主要要求就是让我们去破解**“反爬机制”**,即反“反爬”,脚本处理层面一般都是decipher网站对request设置的cipher,比如破解一个DES/AES加密拿到key。这篇文章先不去谈…...

Ubuntu中MySQL安装-02
服务器端安装 安装服务器端:在终端中输入如下命令,回车后,然后按照提示输入 sudo apt-get install mysql-server 当前使用的ubuntu镜像中已经安装好了mysql服务器端,无需再安装,并且设置成了开机自启动服务器用于接…...

大数据相关职位介绍之一(数据分析,数据开发,数据产品经理,数据运营)
大数据相关职位介绍之一 随着大数据、人工智能(AI)和机器学习的快速发展,数据分析与管理已经成为各行各业的重要组成部分。从互联网公司到传统行业的数字转型,数据相关职位在中国日益成为推动企业创新和提升竞争力的关键力量。以…...

使用DeepSeek API生成Markdown文件
DeepSeek技术应用与代码实现 一、DeepSeek简介 DeepSeek是一款强大的人工智能写作助手,能够根据用户输入的提示(Prompt)快速生成高质量的文章。它不仅支持批量生成文章,还能通过智能分段、Markdown转HTML等功能优化内容。此外&…...

java多线程学习笔记
文章目录 关键词1.什么是多线程以及使用场景?2.并发与并行3.多线程实现3.1继承 Thread 类实现3.2Runnable 接口方式实现3.3Callable接口/Future接口实现3.4三种方式总结 4.常见的成员方法(重点记忆)94.1setName/currentThread/sleep要点4.2线程的优先级…...

Manticore Search,新一代搜索引擎之王
吊打ES,新一代搜索引擎之王 概述 Manticore Search 是一个开源的分布式搜索引擎,专注于高性能和低延迟的搜索场景。 它基于 Sphinx 搜索引擎开发,继承了 Sphinx 的高效索引和查询能力,并在分布式架构、实时搜索、易用性等方面进…...

【MySQL】数据类型与表约束
目录 数据类型分类 数值类型 tinyint类型 bit类型 小数类型 字符串类型 日期和时间类型 enum和set 表的约束 空属性 默认值 列描述 zerofill 主键 自增长 唯一键 外键 数据类型分类 数值类型 tinyint类型 MySQL中,整形可以是有符号和无符号的&…...
CAG技术:提升LLM响应速度与质量
标题:CAG技术:提升LLM响应速度与质量 文章信息摘要: CAG(Cache-Augmented Generation)通过预加载相关知识到LLM的扩展上下文中,显著减少了检索延迟和错误,从而提升了响应速度和质量。与传统的R…...
上位机知识篇---Linux源码编译安装链接命令
文章目录 前言第一部分:Linux源码编译安装1. 安装编译工具2. 下载源代码3. 解压源代码4. 配置5. 编译6. 测试(可选)7. 安装8. 清理(可选)9.注意事项 第二部分:链接命令硬链接(Hard Link…...

科研绘图系列:R语言绘制线性回归连线图(line chart)
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理画图保存图片系统信息参考介绍 科研绘图系列:R语言绘制线性回归连线图(line chart) 加载R包 library(tidyverse) library(ggthemes) libra…...

将ollama迁移到其他盘(eg:F盘)
文章目录 1.迁移ollama的安装目录2.修改环境变量3.验证 背景:在windows操作系统中进行操作 相关阅读 :本地部署deepseek模型步骤 1.迁移ollama的安装目录 因为ollama默认安装在C盘,所以只能安装好之后再进行手动迁移位置。 # 1.迁移Ollama可…...

Oracle 创建用户和表空间
Oracle 创建用户和表空间 使用sys 账户登录 建立临时表空间 --建立临时表空间 CREATE TEMPORARY TABLESPACE TEMP_POS --创建名为TEMP_POS的临时表空间 TEMPFILE /oracle/oradata/POS/TEMP_POS.DBF -- 临时文件 SIZE 50M -- 其初始大小为50M AUTOEXTEND ON -- 支持…...

cursor ide配置远程ssh qt c++开发环境过程记录
cursor是啥就不介绍了,好像是目前最好用的ai ide,下面主要是配置远程ssh连接linux机器进行qt5 c程序运行的配置过程记录。 一、c_cpp_properties.json 在项目根目录的.vscode目录里面新建c_cpp_properties.json文件,根据你的实际情况配置该文…...

yolov5错误更改与相关参数详解(train.py)
1.错误更改 main中相关参数 if __name__ __main__:parser argparse.ArgumentParser()parser.add_argument(--weights, typestr, default, helpinitial weights path)parser.add_argument(--cfg, typestr, defaultmodels/yolov5s.yaml, helpmodel.yaml path)parser.add_arg…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

Unity3D中Gfx.WaitForPresent优化方案
前言 在Unity中,Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染(即CPU被阻塞),这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案: 对惹,这里有一个游戏开发交流小组&…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...