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…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
Java多线程实现之Callable接口深度解析
Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...