可碧教你C++——哈希


在文章的开始,先祝大家牢大年快乐


哈希的简介

unordered系列

在C++11里,加入了两个新的container——unordered_set和unordered_map

其使用方式与map和set类似,但是其底层实现则与其完全不同。set和map的底层数据结构为红黑树,而unordered系列的底层数据结构则为哈希表。哈希表的特点是无法按照数据大小进行排列,但是相应的其效率比红黑树稍微高一些。

但是,哈希并非是狭义的一种数据结构。哈希是一种思想,在接触哈希表之前,其实我们就已经接触到了很多哈希的知识。比如统计字符串中各个字符出现的次数,我们可以先开辟一个26个空间的数组,然后根据字符c-'a'来直接对应位置,这便是哈希。

哈希的例题

217. 存在重复元素 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/contains-duplicate/description/

何为哈希?

就像我们看到鸡就会想到鸽鸽,看到雪豹就会想到芝士

如我们将字符映射到数组中的具体位置一样,哈希代表的是一层或多层映射关系。哈希通过某种算法将一个值映射为另一个易于保存或者易于查找的值,当我们保存一个值时便通过这某一算法将数据转化为另一数据进行保存,而查找时也通过查找转化后的数据间接进行查找,最终这一算法和容器的实现便被称作为——哈希表


哈希表

哈希表是一个数组。但是其插入元素并非线性插入,而是根据某种映射关系,将元素插入到相应映射的位置,从而方便查找。也就是说,哈希表的插入和查找都是跳跃式的直接访问,通过这种跳跃将线性的查找复杂度急剧下降。

哈希有种种映射,但是哈希表只有一种。所以,哈希表在实现的时候,只采用了一种很常用的映射关系——除余映射。也就是把每一个进入哈希表的数除余一个数,得到的结果为哈希表对应的结果。

打个比方,一个哈希表有10个空间,于是便可以将插入哈希表的所有数除余10,31就对应空间1,52就对应空间2,以此类推。但是,我们又不可避免会面临另一个问题:31和61除余10都等于1,但是空间1只有一个,应该怎么办?这类问题我们统称为——哈希冲突

哈希冲突

两个不同的值通过某个哈希函数映射到了同一个位置,便被称为哈希冲突。

哈希冲突是不可避免的。因为数据是无限的,而空间是有限的,无论空间开辟多大,最终都会有两种相同的哈希映射值出现。但是,我们可以采取多种方法去避免哈希冲突,一共可以分为三类——多重哈希,闭散列和开散列。

多重哈希

虽然我们采用了除余的哈希映射方式,但是这并不代表我们只能使用这一个哈希映射。我们可以先通过多几层的哈希映射,让这些数据的关联性尽可能降低,最后再用除余映射定位,避免哈希冲突的出现。

同样,当我们将字符串存储入哈希表的时候,其实就已经进行了一次映射。字符串是没有办法除余一个整数的,我们先通过映射将字符串变为一个整数,然后再用映射后的整数来进行除余操作。

但是,如果我们使用传统的ASCII码值对应,问题就大了:

kobe和kboe的ASCII码值和相同,但是其哈希冲突了。

对于这个问题,其实一直都没有一个很好的解决方案。各大数学家在这类问题上进行了很多研究,也提出了很多解决方案,但是各有利弊,其中综合下来还是有着一个最好的算法来避免字符串的哈希冲突:

字符串的哈希算法icon-default.png?t=N7T8https://www.cnblogs.com/-clq/archive/2012/05/31/2528153.html

当然,具体原因我们便不多做深究了,以上内容的高深算法也只是了解,只需要记住科学家们交给我们的结论就可以了。

闭散列

闭散列是解决哈希冲突一个很简单粗暴的方法,相应带来的后果便是不怎么常用。哈希冲突了,那就把冲突的元素肘开,一直肘到没有冲突的地方为止。

但是这种方法会存在一个问题:假如我们把哈希表的所有元素都初始置为0,但是如果我们真的要插入0,那我们咋知道这个0是原本初始化的还是被插入的?

其中一个很简单的解决方法,我们将数组的每一个空间都保存一个状态,通过该状态来判断这个空间存储的元素是否有效

//状态
enum state
{EMPTY,EXIST,DELETE
};//数组每一个空间存储的元素
template<class K,class V>
struct elem
{K _key;V _val;state _st;//存储一个状态
};

同时,有这个状态的存储,我们就无需再在哈希表的删除上多下功夫。我们只需要把这个元素的状态由EXIST变为DELETE,就表示这块空间的元素已经被删除了。

负载因子

我们在制作这个哈希表的时候,很容易会发现几个问题:

  • 哈希表的空间是有限的,但是插入的元素是无限的,哈希表总有扩容的时候
  • 如果哈希表濒临满表,那会不断产生哈希冲突,最终就变成了一个不具有映射特征的数组
  • 当哈希表扩容之后,原本数组的大小发生了变化,哈希映射也发生了变化

而解决这几个问题,也便引入了一个新的概念——负载因子。

负载因子代表数组所承受的元素的比例,比如一个数组容量是100,存储了40个元素,那负载因子便为0.4;对于闭散列,负载因子最大的时候自然是数组存满的时候为1. 

负载因子有什么用?其作用便是解决第二个问题。负载因子越大,数组越满,哈希冲突便越多,而为了不过多去破化哈希映射的关系,当负载因子超过一定大小时,数组便需要进行扩容,并非只有数组满了才进行扩容

而哈希表的扩容,因为哈希映射关系是对容量的除余,容量发生了变化,哈希映射也发生了变化。所以, 哈希表的扩容不单单是copy整个数组,哈希表的扩容需要将原表的每个元素都重新计算哈希映射关系,然后插入到新表里。

闭散列哈希表的实现

一般的,我们将最大负载因子设为0.75,将哈希表初容量设为10

每次插入新元素的时候,我们要先检查负载因子是否超过最大值,如果超过了则需要扩容。

//线性探测哈希闭散列
template<class K,class V>//key-val键对模型
class HashTable
{enum state//状态{EMPTY,EXIST,DELETE};struct elem//数组的每一个元素{K _key;V _val;state _st;};
public:HashTable(size_t capacity=10):_size(0){_table.resize(capacity);}bool insert(const K& key, const V& val){CheckFactor();//检查是否超过最大负载因子size_t HashAddress = key % _table.capacity();//哈希映射while (_table[HashAddress]._st == EXIST)//如果哈希冲突,则继续向后找空余位置{if (_table[HashAddress]._key == key)//如果哈希表中已有该元素,则插入失败return false;HashAddress++;if (HashAddress == _table.capacity())//到哈希表末尾时,返回从头开始查找HashAddress = 0;}//当状态是EMPTY或者DELETE时,才可以插入_table[HashAddress]._key = key;_table[HashAddress]._val = val;_table[HashAddress]._st = EXIST;_size++;return true;}int find(const K& key){size_t HashAddress = key % _table.capacity();//哈希映射while (!(_table[HashAddress]._st == EMPTY))//因为负载因子小于1,所以一定有空余空间{if (_table[HashAddress]._st == EXIST && _table[HashAddress]._key == key)return HashAddress;HashAddress++;if (HashAddress == _table.capacity())HashAddress = 0;}return -1;//如果没有找到,则返回-1}bool erase(const K& key){int HashAddress = find(key);if (key == -1)return false;_table[HashAddress]._st = DELETE;_size--;return true;}size_t size() const{return _size;}bool empty() const{return _size == 0;}
private:vector<elem> _table;size_t _size;float MaxLoadFactor = 0.75;//负载因子bool CheckFactor(){if ((float)_size / _table.capacity() >= MaxLoadFactor)//检查负载因子是否大于0.75{HashTable<K, V> newtable(_table.capacity() * 2);for (auto e : _table)//将每一个元素放入新表{if (e._st == EXIST){newtable.insert(e._key,e._val);}}swap(_table, newtable._table);//交换旧表和新表,旧表会自动释放}return true;}
};

开散列

 和闭散列类似,用一张图便可以概括开散列

所以,开散列又叫为哈希桶,数组中每一块空间所存的不再是单个元素,而是一个单链表,如果产生了哈希冲突,便将单链表中向下插入新元素,而非通过移位来避免哈希冲突。

而如果某个单链表的数据量过大, 还可以将单链表转换为红黑树,来提高查找效率。

那哈希桶需不需要负载因子呢?当然。 如果数据量过多,每个单链表的长度变大,那么最终哈希表的查找消耗就变成了单链表的查找消耗,为线性消耗,得不偿失。同时因此,就算哈希冲突可以解决,我们也要尽量避免哈希冲突来减少查找的消耗。

开散列哈希桶的实现

因为哈希桶每一个空间可以存储多个数据,所以我们负载因子可以放大为1。 

而扩容的时候,我们一样需要重新寻找映射,然后在新表中插入。

//哈希桶开散列
template<class K,class V>
class HashBucket
{struct ListNode{K _key;V _val;ListNode* next=nullptr;};	
public:HashBucket(size_t capacity = 10):_size(0){_bucket.resize(capacity);}~HashBucket(){for (auto e : _bucket){ListNode* cur = e;while (cur){ListNode* next = cur->next;delete cur;cur = next;}}}bool insert(const K& key, const V& val){if (find(key))return false;CheckFactor();//插入:单链表的头插size_t HashAddress = key % _bucket.capacity();ListNode* NewNode = new ListNode;NewNode->_key = key;NewNode->_val = val;NewNode->next = _bucket[HashAddress];_bucket[HashAddress] = NewNode;_size++;return true;}ListNode* find(const K& key){size_t HashAddress = key % _bucket.capacity();ListNode* cur = _bucket[HashAddress];while (cur){ListNode* next = cur->next;if (cur->_key == key)return cur;cur = next;}return nullptr;}bool erase(const K& key){size_t HashAddress = key % _bucket.capacity();ListNode* pre = nullptr;ListNode* cur = _bucket[HashAddress];while (cur){if (cur->_key == key){if (pre == nullptr){_bucket[HashAddress] = cur->next;}else{pre->next = cur->next;}delete cur;return true;}pre = cur;cur = cur->next;}return false;}
private:vector<ListNode*> _bucket;size_t _size;float MaxLoadFactor = 1;void CheckFactor(){if ((float)_size / _bucket.capacity() >= MaxLoadFactor){HashBucket<K, V> newbucket(_bucket.capacity() * 2);for (auto& e : _bucket){ListNode* cur = e;while (cur){ListNode* next = cur->next;size_t HashAddress = cur->_key % newbucket._bucket.capacity();//把每一个元素头插到新表的对应位置cur->next = newbucket._bucket[HashAddress];newbucket._bucket[HashAddress] = cur;cur = next;}e = nullptr;}_bucket.swap(newbucket._bucket);}}
};

有人可能要问,单链表的插入和删除这么麻烦,那为什么不用双链表来实现?

别问,库里就是用的单链表 


哈希的应用

篇幅所限,哈希的应用放在了另一篇文章,具体的应用有位图,布隆过滤器,哈希切割 

哈希的应用icon-default.png?t=N7T8http://t.csdnimg.cn/xepez


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

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

相关文章

Umbraco:从产品介绍到实战应用

一、产品介绍 Umbraco是一个开源的内容管理系统&#xff08;CMS&#xff09;&#xff0c;以其灵活性和可扩展性而闻名。Umbraco允许开发人员根据需要定制系统&#xff0c;同时为非技术人员提供了一个直观的界面来管理网站内容。Umbraco诞生于丹麦&#xff0c;经过多年的发展&a…

2023年阿里云云栖大会:前沿技术发布与未来展望

在2023年的阿里云云栖大会上&#xff0c;我见证了云计算和人工智能领域的又一历史性时刻。这次大会不仅是对未来科技趋势的一次深入探索&#xff0c;更是阿里云技术实力和创新能力的集中展示。 首先&#xff0c;千亿级参数规模的大模型通义千问2.0的发布&#xff0c;无疑将人工…

Flask修改Response Headers中的Server值

Headers中的Server会暴露出Python版本&#xff0c;导致的结果就是方便被渗透快速定位Python版本后找到对应版本的漏洞&#xff0c;因此导致网络安全问题 伪方法&#xff1a; 像这个马上就暴露出Python版本&#xff0c;如何解决这个网络上有说直接用response.headers.remove(Ser…

Mysql : command not found

1.Mysql : command not found 安装成功的mysql&#xff0c;并且服务已经启动&#xff0c;查看进行是可以看到的&#xff0c;但是使用命令登录操作&#xff0c;却抛出错误&#xff1a;command not found。 2.解决方案 2.1 查看/usr/bin目录下是否有mysql服务连接 ls /usr/bin…

1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程

本篇文章主要讲解1panel中的sftpgo webadmin 更新修改docker容器文件的配置教程&#xff0c;适合sftpgo webadmin和1panel系统用户配置时使用。 作者&#xff1a;任聪聪 rccblogs.com 日期&#xff1a;2024年1月8日 sftpgo是无法直接直接更改容器内部的网站目录的&#xff0c;但…

Python进阶之元类

Python进阶之元类 目录 什么是元类&#xff1f; 元类的调用流程 根据类自定义元类 __new__方法以及参数 ----------cls ----------name ----------bases ----------attrs __call__方法 生成对象的完整代码 什么是元类&#xff1f; 在python面向对象中&#xff0c;我们知道所有…

AI大语言模型会带来了新一波人工智能浪潮?

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

自动化测试最佳实践:有效利用 ForEach 循环技巧

在 自动化测试 场景中&#xff0c;当需要对数组内的所有元素分别执行特定操作时&#xff0c;我们通常会采用 ForEach 循环 来实现这一过程。例如一个常见的场景&#xff1a;请求一个获取宠物列表的接口&#xff0c;返回了 n 个宠物的 id &#xff0c;然后根据这些宠物 id 逐一查…

mongodb学习篇

目录 前言基本概念数据库-database集合-collection文档-document 部署mongodblinux安装mongodbdocker安装mongodb MongoDB Shell (mongosh)命令行工具mongodb可视化-mongodb-compass、mongo-expressmongodb配置文件mongodb库、集合、文档库基本操作集合基本操作文档的增删改查C…

世微AP3464 车充专用芯片 4-30V输入 ADJ可调/2.4A输出降压驱动芯片

AP3464 是一款支持宽电压输入的同步降压 电源管理芯片&#xff0c;输入电压 4-30V 范围内可实现 2.4A 的连续电流输出。通过调节 FB 端口的分压 电阻&#xff0c;设定输出 1.8V 到 28V 的稳定电压。 AP3464 具有的恒压/恒流(CC/CV)特性。 AP3464 采用电流模式的环路控制原理&am…

获取小红书笔记详情API调用说明(含请求示例参数说明)

前言 小红书&#xff0c;是一个引领全球时尚潮流的社交电商平台。在这里&#xff0c;你可以发现世界各地的优质好物&#xff0c;从美妆护肤、穿搭时尚&#xff0c;到家居生活、旅行美食&#xff0c;一切应有尽有。同时&#xff0c;这里也是一个分享生活点滴的平台&#xff0c;…

sonarqube配置本地扫描代码

一、本地maven设置setting文件&#xff1a; 1&#xff09;添加pluginGroup <pluginGroups><pluginGroup>org.sonarsource.scanner.maven</pluginGroup></pluginGroups> 2&#xff09;添加profile&#xff1a; <profile><id>sonar</i…

有趣的前端知识(二)

推荐阅读 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;一&#xff09; 智能化校园&#xff1a;深入探讨云端管理系统设计与实现&#xff08;二&#xff09; 文章目录 推荐阅读HTML元素元素属性头部元素列表元素区块元素表单元素 颜色字符实体 HTML元素 …

知识点整理[(GraphGeo)RELATED WORK]

2 RELATED WORK 2.1 IP Geolocation 问题一:IP定位预测方法之一:Data mining-based methods 回答: 依赖于在公开的资源中挖掘位置线索来对目标IP(target IP)进行地理定位。其中一些数据分析了来自与IP相关的数据库,如WHOIS数据库和DNS的数据。 (1)例如,Moore等…

neo4j图数据库的简单操作记录

知识图谱文件导出 首先停止运行sudo neo4j stop然后导出数据库 导出格式为&#xff1a; 具体命令如下sudo neo4j-admin database dump --to-path/home/ neo4j最后重启sudo neo4j start知识图谱外观修改 在网页点击节点&#xff0c;选中一个表情后点击&#xff0c;可修改其颜…

Python+Flask+MySQL的图书馆管理系统【附源码,运行简单】

PythonFlaskMySQL的图书馆管理系统【附源码&#xff0c;运行简单】 总览 1、《图书馆管理系统》1.1 方案设计说明书设计目标需求分析工具列表 2、详细设计2.1 登录2.2 注册2.3 程序主页面2.4 图书新增界面2.5 图书信息修改界面2.6 普通用户界面2.7 其他功能贴图 3、下载 总览 …

【sklearn练习】preprocessing的使用

介绍 scikit-learn 中的 preprocessing 模块提供了多种数据预处理工具&#xff0c;用于准备和转换数据以供机器学习模型使用。这些工具可以帮助您处理数据中的缺失值、标准化特征、编码分类变量、降维等。以下是一些常见的 preprocessing 模块中的功能和用法示例&#xff1a; …

【CSS】CSS中的BFC,是什么?

一、常见定位方案 普通流默认&#xff0c;从上而下&#xff0c;行内元素水平排列&#xff0c;行满换行&#xff0c;块级元素渲染成一个新行。 浮动先按普通流位置出现&#xff0c;然后根据浮动方向偏移。 绝对定位元素具体位置由绝对定位坐标组成。二、什么是BFC BFC&#xff…

适合小微企业的记账软件有哪些?

“涉及了很多的库存管理、进销存和记账&#xff0c;最好是这个软件可以随时看到我们公司的财务数据&#xff0c;且一定要安全&#xff01;” 进、销、存、财……进销存财管理就像是企业的大管家&#xff0c;管着货物、资金、成本、收入&#xff0c;保证一切有序运转。这些管理…

ISIS基本概率与配置(HCIP完整版)

目录 一、ISIS协议基础 1、ISIS概述&#xff08;认识ISIS&#xff09; 2、ISIS的应用 4、ISIS的工作过程 5、ISIS路由器的类型 6、ISIS区域 7、ISIS报文 8、ISIS基础配置 9、进程号&#xff1a; 10、NET地址 11、ISIS邻居关系 二、邻居表分析 1、ISIS邻居表字段解析…