【C++高阶(五)】哈希思想--哈希表哈希桶

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C++从入门到精通⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习C++
  🔝🔝


在这里插入图片描述

哈希结构

  • 1. 前言
  • 2. unordered系列容器
  • 3. 哈希概念以及哈希结构
  • 4. 哈希表详解(闭散列)
  • 5. 哈希表模拟实现
  • 6. 哈希桶详解(开散列)
  • 7. 哈希桶模拟实现
  • 8. 对于哈希结构的思考

1. 前言

相信大家一定听说过大名鼎鼎的
哈希结构吧,就算是没用过,也听说
过这句话:这道题无脑哈希就能做

哈希,哈希,到底什么是哈希?本篇文章
将带大家彻底搞懂这个问题!

本章重点:

本篇文章着重讲解关联式容器
unordered_map&set的底层结构
以及它们的模拟实现.并且将给大家
介绍unorder系列的接口函数!


2. unordered系列容器

不知道大家在刷题时有没有看见过
unordered_map和unordered_set
它们与map&set是什么关系?
什么时候可以用unordered系列?

带着这些疑问,进行今天的学习:
在这里插入图片描述

  1. unordered_map是存储<key, value>键值对的关联式容器,其允许通过keys快速的索引到与其对应的value。
  2. 在unordered_map中,键值通常用于惟一地标识元素,而映射值是一个对象,其内容与此键关联。键和映射值的类型可能不同。
  3. 在内部,unordered_map没有对<kye, value>按照任何特定的顺序排序
  4. unordered_map容器通过key访问单个元素要比map快,但它通常在遍历元素子集的范围迭代方面效率较低。
  5. unordered_maps实现了直接访问操作符(operator[]),它允许使用key作为参数直接访问value。

可以发现,其实unordered_map和
map使用起来没什么区别,可以说
是一模一样,那么什么时候应该用
unordered系列呢?答案是你只关
心键值对的内容而不关心是否有序
时,选择unordered系列

同理,unordered_set和set的用法
也基本一致,这里就不多做介绍了
如果你不知道map和set的用法,请
先看这篇文章:

map和set的熟悉


3. 哈希概念以及哈希结构

unordered_map&set的底层
结构实际上是哈希桶,也就是
哈希结构,下面来了解一下哈希思想:

最简易的哈希思想,数组下标0到100
存储的值代表数字0到100存不存在

在这里插入图片描述

当然,实际情况下不可能最大值是几
就开辟多大的数组,因为会造成空间
的浪费,哈希的思路一般是根据某种
映射关系,把数据映射到数组中,查找
时也使用同样的映射关系来查找!

在这里插入图片描述
当然,当插入4后再插入14,此时会有问题
因为4这个位置已经被占用了,再次映射到
这个位置明显是行不通的,这个过程被称为
哈希冲突,具体内容会在后面讲解!

哈希结构又分为哈希表和哈希桶
下面就来一一讲解这两个的区别


4. 哈希表详解(闭散列)

引起哈希冲突的一个原因可能是:
哈希函数设计不够合理

在这里插入图片描述
然而不管哈希函数再怎么设计,都不能
完全保证不同的值映射到同一位置,所以
引申出了闭散列和开散列的解决方法

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

寻找下一个空位置的方法有很多,如
线性探测(挨个往后找)
二次探测(以2^i为单位向后找)

这里只讲解线性探测

在这里插入图片描述
插入44后,位置4被占用了就往后找空位

哈希表的删除以及查找操作:

哈希表中的元素如果只是原生数据类型,
那么我们将4删除后,再去查找4肯定是找
不到的,但是此时去查找44也会找不到,因
为44本来应该映射到4位置,但是由于哈希
冲突跑到了8位置,并且我们并不知道它在
哪个位置,所以查找时会找不到!

解决方案:

存储数据不单单存储原生类型
再给每一个位置加上一个状态枚举
分别代表此位置是空,被删除还是有数

// 哈希表每个空间给个标记
// EMPTY此位置空, EXIST此位置已经有元素, DELETE元素已经删除
enum State {EMPTY, EXIST, DELETE};

查找元素时,若此位置是删除或存在
状态就继续向后找,若是空就代表此
元素并不在哈希表中!


5. 哈希表模拟实现

首先我们先将整个结构框架写出来:

enum State
{EMPTY,EXIST,DELETE
};template<class K, class V>
struct HashData
{pair<K, V> _kv;State _state;HashData(const pair<K, V>& kv = make_pair(0, 0)):_kv(kv),_state(EMPTY){ }
};template<class K, class V>
class HashTable
{
private:vector<HashData<K, V>> _table;//数组中存储HashData封装的数据size_t _size = 0; //有效数据的个数
};

再来探讨一下插入时的扩容规则:

由于哈希表采用的是向后探测的方法
来存放不同的数据,那么当数据的个数
和数组的大小很接近时,会有很多冲突,
所以在容量到0.7或0.8时就应该要扩容了!
并且在扩容后,数据要重新根据先有的规则
进行挪动,也就是将旧数据挪动到新表!

bool insert(const pair<K, V>& kv)
{if (_table.size() == 0 || 10 * _size / _table.size() >= 7) // 扩容{size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;HashTable<K, V> newHT;newHT._table.resize(newSize);// 旧表的数据映射到新表for (auto e : _table){if (e._state == EXIST){newHT.insert(e._kv);}}_table.swap(newHT._table);}size_t index = kv.first % _table.size();//不能模capacity,如果模出来的数大于size了还插入进去了会报错//线性探测while (_table[index]._state == EXIST){index++;index %= _table.size();//过大会重新回到起点}_table[index]._kv = kv;_table[index]._state = EXIST;_size++;return true;
}HashData<K, V>* find(const K& key)
{if (_table.size() == 0)return nullptr;size_t index = key % _table.size();//负数会提升成无符号数,所以负数不影响结果,但是string类不能取模,需要加入一个仿函数size_t start = index;while (_table[index]._state != EMPTY){if (_table[index]._kv.first == key && _table[index]._state == EXIST)return &_table[index];index++;index %= _table.size();if (index == start)//全是DELETE时,必要时会breakbreak;}return nullptr;
}bool erase(const K& key)
{HashData<K, V>* ret = find(key);if (ret){ret->_state = DELETE;--_size;return true;}return false;
}

6. 哈希桶详解(开散列)

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中

哈希桶实际上是这样的结构:

在这里插入图片描述

看似是一格数据,其实是一个链表指针

并且开散列的扩容旧不需要像
闭散列一样到0.7旧扩容了

在这里插入图片描述

可以把数组的每一个位置想象成
一个抽屉,当你远观时它就是一个
单一的格子,当你仔细把玩时它就
是一个可以拉开的存储结构!


7. 哈希桶模拟实现

首先先把基础框架写出来:

template<class K,class V>
struct HashNode
{pair<K, V> _kv;HashNode<K, V>* _next;//以单链表的方式链接HashNode(const pair<K,V>& kv):_kv(kv),_next(nullptr){}
};template<class K,class V>
class HashTable
{typedef HashNode<K, V> Node;
private:vector<Node*> _table;size_t _size = 0;//有效数据个数
};

下一步,将新来的元素头插到链表中
因为头插的效率是O(1),并且扩容后
的策略和哈希表一样,重新将数据映射
到新表中

bool insert(const pair<K, V>& kv)
{//去重+扩容if (find(kv.first))return false;//负载因子到1就扩容if (_size == _table.size()){vector<Node*> newT;size_t newSize = _table.size() == 0 ? 10 : _table.size() * 2;newT.resize(newSize, nullptr);//将旧表中的节点移动到新表for (int i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;size_t hashi = cur->_kv.first % newT.size();cur->_next = newT[hashi];newT[i] = cur;cur = next;}_table[i] == nullptr;}_table.swap(newT);}size_t hashi = kv.first % _table.size();//头插Node* newnode = new Node(kv);newnode->_next = _table[hashi];_table[hashi] = newnode;++_size;return true;
}Node* find(const K& key)
{if (_table.size() == 0)return nullptr;size_t hashi = key % _table.size();Node* cur = _table[hashi];while (cur)//走到空还没有就是没用此数据{if (cur->_kv.first == key)return cur;cur = cur->_next;}return nullptr;
}bool erase(const K& key)
{Node* ret = find(key);if (ret == nullptr)return false;size_t hashi = key % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur && cur->_kv.first != key)//找到要删除的节点{prev = cur;cur = cur->_next;}Node* next = cur->_next;if (cur == _table[hashi])//注意头删的情况_table[hashi] = next;elseprev->_next = next;delete cur;cur = nullptr;_size--;return true;
}

对代码的解释都在注释中,还有问题欢迎私信!


8. 对于哈希结构的思考

我们会发现一个问题,不管是哈希
表还是哈希桶,都用到了cur.first模
上一个数,但是如果cur.first不是整型
不能取模怎么办?(如字符串)

这时需要在哈希类中再传入一个模板
参数,此模板参数为仿函数,只需将写好
的仿函数传入即可进行取模,比如string
仿函数可以这样写:

template<>
struct HashFunc<string>
{//BKDR算法:将字符串转换为整数size_t operator()(const string& str){size_t sum = 0;for (auto ch : str){sum *= 131;sum += (size_t)ch;}return sum;//将字符的asc码全部加起来再返回}
};

🔎 下期预告:哈希思想的应用🔍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/173298.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读:C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range SLAM

前言 论文全程为C2VIR-SLAM: Centralized Collaborative Visual-Inertial-Range Simultaneous Localization and Mapping&#xff0c;是发表在MDPI drones&#xff08;二区&#xff0c;IF4.8&#xff09;上的一篇论文。这篇文章使用单目相机、惯性测量单元( IMU )和UWB设备作为…

多货币转换多语言切换的跨境电商源码,实现全球购物的自由流通

WoShop跨境电商源码 在全球经济一体化的今天&#xff0c;跨境电商的发展日益蓬勃。为了满足不同国家和地区用户的购物需求&#xff0c;我们开发了一款多货币转换多语言切换的跨境电商源码&#xff0c;实现全球购物的自由流通。 一、多货币转换 在跨境电商交易中&#xff0c;货币…

关于铝镓氮(AlGaN)上p-GaN的高选择性、低损伤蚀刻

引言 GaN基高电子迁移率晶体管&#xff08;HEMT&#xff09;由于其高频和低导通电阻的特性&#xff0c;近来在功率开关应用中引起了广泛关注。二维电子气&#xff08;2DEG&#xff09;是由AlGaN/GaN异质结中强烈的自发和压电极化效应引起的&#xff0c;这导致传统器件通常处于…

FinGPT:金融垂类大模型架构

Overview 动机 架构 底座模型&#xff1a; Llama2Chatglm2 Lora训练 技术路径 自动收集数据并整理 指令微调 舆情分析 搜新闻然后相似搜索 检索增强架构 智能投顾 Hugging face 地址 学术成果及未来方向 参考资料

开源万能DIY预约小程序源码系统+自由DIY,海量模板任选择,附带完整的搭建教程

在移动互联网时代&#xff0c;用户对于预约服务的便捷性和高效性需求日益增长。为了满足这一需求&#xff0c;我们凭借多年的技术积累和经验&#xff0c;开发出了这款开源万能DIY预约小程序源码系统。该系统的推出旨在帮助开发者快速构建功能丰富、符合用户需求的预约小程序&am…

[ CSS ] 内容超出容器后 以...省略

内容超出容器后 以…省略 当前效果 代码 <template><div class"box">有志者&#xff0c;事竟成&#xff0c;破釜沉舟&#xff0c;百二秦关终属楚; 有心人&#xff0c;天不负&#xff0c;卧薪尝胆&#xff0c;三千越甲可吞吴</div> </templa…

TechSmith Camtasia2024中文版简单好用的视频处理软件

TechSmith Camtasia 2024中文版是由techsmith公司推出的一款简单好用的视频处理软件&#xff0c;它集视频录制与视频后期处理为一体&#xff0c;用户可以使用软件来进行屏幕录制&#xff0c;其中包括了影像、音效、鼠标移动的轨迹、解说声音等任何模式下的电脑屏幕状态&#xf…

第二证券:煤炭板块震荡走高 潞安环能、晋控煤业涨超5%

证券时报网讯&#xff0c;煤炭板块27日盘中发力走高&#xff0c;到发稿&#xff0c;潞安环能、晋控煤业涨超5%&#xff0c;平煤股份、山西焦煤涨逾3%&#xff0c;恒源煤电、开滦股份等上扬。 职业方面&#xff0c;近期寒潮来袭&#xff0c;气温下降带动居民用电需求增加&#…

Mysql使用周期性计划任务定时备份,发现备份的文件都是空的?为什么?如何解决?

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…

某生物科技巨头:引入安全工具,推动基因科技领域智能化发展

某生物科技巨头是生物科技领域的领导者&#xff0c;业务覆盖行业全产业链、全应用领域&#xff0c;是全球领先的科学技术服务提供商和精准医疗服务运营商。一直以来&#xff0c;该生物科技机构都致力于加速推动以基因科技为支撑的生命数字化建设&#xff0c;实现批量短基因快速…

小型内衣洗衣机什么牌子好?口碑最好的小型洗衣机

很多人会觉得内衣洗衣机是智商税&#xff0c;洗个内衣只需要两分钟的事情&#xff0c;需要花个几百块钱去入手一个洗衣机吗&#xff1f;然而清洗贴身衣物的并不是一件简单的事情&#xff0c;如果只是简单的搓洗&#xff0c;内裤上看不见的细菌也无法消除&#xff0c;而且对来生…

【Ambari】HDP单机自动化安装(基础环境和MySQL脚本一键安装)

&#x1f984; 个人主页——&#x1f390;开着拖拉机回家_Linux,大数据运维-CSDN博客 &#x1f390;✨&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341;&#x1fa81;&#x1f341; &#x1fa81;&#x1f341;&#x1fa81;&am…

04:2440---内存控制器

目录 一:介绍 1:引入 2:概念 3:通信 A:片选信号 B:片选信号的地址空间范围 ​​​​ 4:地址线 A:不同位数的接法 B:访问原理 C:访问地址 5:时序 1:NOR FLASH A:2440NOR FLASH时序 B:原理/时序图 C:寄存器 6:SDARM A:访问方式 B:原理图 C:BWSCON D:BANKCON…

海外Leads Generation产业:中国出海群体的行业大机会

Leads Generation&#xff08;简称LeadsGen&#xff09;指的是集中精力吸引和开发潜在客户的营销策略。通过引导式的营销策略&#xff0c;企业分发内容吸引潜在客户&#xff0c;引导客户留下电话/邮件/姓名等信息。基于这些信息&#xff0c;企业可建立潜在客户数据库&#xff0…

一文读懂:IOPS、延迟和吞吐量等存储性能指标

各位ICT的小伙伴们大家好呀&#xff0c; 在我们谈存储性能的时候&#xff0c;总会听到IOPS、延迟&#xff08;Latency&#xff09;、带宽&#xff08;Bandwidth&#xff09;、吞吐量&#xff08;Throughput&#xff09;以及响应时间&#xff08;Response Time&#xff09;等技…

『Linux升级路』基础开发工具——make/Makefile

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;Linux &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、认识make/Makefile &#x1f4d2;1.1make/Makefile的优点 &#x1f4d2;…

EM32DX-C1【分布式io】

1设备类型&#xff1a; 电压&#xff1a;DC24V 输入16点 输出16点雷赛 EM32DX-C1 模块是一款基于 ASIC 技术的高性能、高可靠性的 CANopen 总线数字 量输入输出扩展模块&#xff0c;具有 16 路通用输入接口和 16 路通用输出接口。输入输出接口均采用光 电隔离和…

【yolov5人行道-斑马线目标检测】

yolov5人行道-斑马线目标检测 数据集yolov5人行道-斑马线目标检测检测模型 数据集 YOLOv5是一种目标检测算法&#xff0c;可以用于检测图像中的人行道-斑马线。在目标检测领域&#xff0c;YOLOv5通过结合多种技术手段&#xff0c;包括使用Mosaic数据增强操作、自适应锚框计算与…

SDN之Ubuntn20.04OpenDaylight控制器的安装

目录 1.OpenDaylight简介2.安装JAVA环境3.安装OpenDaylight4.问题总结 1.OpenDaylight简介 OpenDaylight&#xff08;ODL&#xff09;是一个开源的软件定义网络&#xff08;SDN&#xff09;控制器平台&#xff0c;提供了非常美观且功能完善的可视化管理界面&#xff0c;方便用…

redis Redis::geoAdd 无效,phpstudy 如何升级redis版本

redis 查看当前版本命令 INFO SERVERwindows 版redis 进入下载 geoadd 功能在3.2之后才有的&#xff0c;但是phpstudy提供的最新的版本也是在3.0&#xff0c;所以需要升级下 所以想出一个 挂狗头&#xff0c;卖羊肉的方法&#xff0c;下载windows 的程序&#xff0c;直接替…