当前位置: 首页 > news >正文

哈希——unordered_map以及unordered_set的封装

1.哈希

1.1哈希概念

顺序结构以及平衡树中,元素关键码与其存储位置之间没有对应的关系,因此在查找一个元素时,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为树的高度,即O(log2Nlog_2 Nlog2N),搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。
如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素
重要的就是建立一个有一个的一一映射关系。
插入元素:
根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放
搜索元素:
对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功。
以上两种数据处理方式,就是哈希或者散列的方法。

1.2哈希问题

我们知道因为哈希是数据于数组的位置有了映射关系,那必然会出现这种情况:多个数据映射了数组的同一个位置,那么这种现象就是哈希冲突,又叫做哈希碰撞。

2.处理哈希碰撞

处理哈希冲突一般有两种常见的处理方法,那就是闭散列,以及开散列。

2.1闭散列

闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有空位置,那么可以把key存放到冲突位置中的“下一个” 空位置中去。那如何寻找下一个空位置呢?
这时候就需要我们开始线性探测了。
这里的线性探测就需要我们根据每个数组位置的状态,来判断,如果不是空我们就向后遍历,如果是空,就停止。

3.闭散列代码实现

3.1成员

我们之前讲了,闭散列的遍历需要我们根据每个数据的状态来判断,因此闭散列就必然需要一个状态位,同时这里们同样使用pair的数据对来存储数据,为后面的unordered_map和undered_set的封装做准备。
在这里插入图片描述

3.2插入接口实现

首先我们先不用考虑是否需要扩容的问题,则插入代码一定是:

在这里插入图片描述
关于扩容问题:
1.数据集种,空间利用率高,但遍历效率低
2.数据不集中,空间利用率低,但遍历效率高
因此我们需要一个数据判断是否需要扩容,这时候,就引出负载因子,一般是0.7,我们判断如果数组中的空间利用率高于0.7那么就需要扩容,反之就不需要。
但是扩容后随之而来的问题就是,如果扩容了,那么前面已经插入的数据的位置,就又要改变,难道我们需要重新遍历,然后重新插入吗?显然不是。
我们可以重新开辟一个哈希表,然后让这个哈希表的数组容量是需要扩容的容量的2,倍,然后关于数据的插入,我们可以值调用自己的insert函数,让函数帮助我们实现数据插入的过程。
在这里插入图片描述

3.3Find接口的实现

关于这个接口的实现,重要的还是先求出,所找数据,在哈希表中的下标,然后从这个下标开始往后遍历,接着重要的是判断条件,要知道,即使中间有DETLETE状态的位置,我们依然不能停止遍历,所以,可以用既不为空,有时EXIST的状态来判断循环。
在这里插入图片描述

4.泛型思想在哈希里的体现

我们知道,一个数据结构的重要特性就是有模板,可以复用,同样的我们的哈希表内同样也可以存储各种类型的数据,如果插入的int’类型的数据,那么我们的哈希下标可以直接求出来,但如果我们存储的时string的数据呢?
这里我们不妨仿照之前的map和set以及优先级队列的方法,使用仿函数。
当然这里的仿函数使用也是有两种方法的:
1.直接通过定义时传入仿函数的类型
2.使用模板特化
在这里插入图片描述
在这里插入图片描述
下面这份代码就是模板的特化。

5.开散列

开散列是重要的一种哈希方式,因为后面的哈希方式的map’和set都是用的开散列方式实现的。

5.1开散列概念

开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。
简单来说,开散列里面vector存储的不是数据而是一个Node*的指针,vector相当于指针数组。

6.开散列代码实现

开散列里面存储的是指针,每个指针偶对应着一个单链表,因此:
在这里插入图片描述
在这里插入图片描述

6.1开散列的插入

开散列的插入就是链表的插入,这里这里我们如果用头删的方式实现,然后将新节点设置为hashi的节点,会更简单一些:
在这里插入图片描述
同样的开散列也是需要扩容的,不过开散列扩容的要求是空间利用率等于1
的时候在扩容,同样我们需要设置一个新的散列表,然后接下来就是怎么将原来的散列表的数据正确的插入到新的散列表,难道还是和闭散列表的方式一样吗?这里就不用了,因为我们是链表,我们不妨直接将原链表的节点摘出来,然后将他插入到新的正确的位置上,这样就,同时这里我们因为存储的是node,所以还要自己delet原来的空间。
在这里插入图片描述

6.2开散列的erase

开散列的erase我们需要注意的就是先找到所在的哈希桶,然后删除时要注意保留前后节点,链接在一起:
在这里插入图片描述

7.unordered_map和undered_set的封装

这里的哈希map和set的封装和搜索树的map和set的封装的模板形式是一样的。不过前者底层是一个开散列表,后者底层是一个红黑树。
同样
unordered_map底层是<k,pair<k,v>>类型的
unordered_set的底层是<k,k>类型的
所以这里我们需要对开散列表的节点数据类型,使用模板T来表示,为了完成复用。
同样的为了让map和set使用同一个哈希表但是可以拿出不同的数据,这里我们同样需要在设置一个仿函数,用来特殊处理map和set对于的节点node的T不同数据类型,因为思想和搜索树形式的mpa和set相同,这里不过多解释:
在这里插入图片描述在这里插入图片描述

7.1迭代器的封装

迭代器同样我们需要封装起来,因为我们要做到使用迭代器可以拿到节点的数据,而不是节点,因此需要对他进行各种形式的重载:
在这里插入图片描述

7.2迭代器的++

哈希表的重点和难点就是怎么实现迭代器的++,我们知道,如果是正常的下一个位置不为nullptr,那么迭代器的++就是:
cur=cur->next;
return cur;即可
但是如果我们的下一个位置是nullptr呢?
所以这里我们不妨将这个哈希表指针引入我们的迭代器。
有了HashTable,那么我们就可以通过遍历这个HashTable来找寻下一个桶,所以;
在这里插入图片描述

7.3const迭代器和普通迭代器

我们上面的代码已经给出了,为了让一份封装的迭代器实现两个不同的迭代器类型,我们采用来模板的方式,诸多细节桶map和set的树的方式相同,而且关于两者的转换和map和set相同,这一部分呢可以参考上篇文章。

8.总结

哈希是数据结构,但是unordered_map和unordered_set,是一种容器,前者数据结构的分析算是我们新讨论的内容,但是后面的容器包装,大家可以多参考上文中map和set的树形式的封装,难点很多,但理清楚关系后,就很清晰。

http://www.dtcms.com/a/549952.html

相关文章:

  • Java 的演进与现代应用:从经典语言到云时代中坚力量
  • Slicer中启动器的生成过程
  • html5手机网站开发工具响应式网站和自适应
  • 百度快照 直接进网站中核二二建设有限公司
  • 工具与业务流程脱节时如何解决
  • h5游戏免费下载:石头剪刀布
  • 网站备案信息抽查阳江网站建设 公司
  • html5 网站模板下载建设网站的方案
  • Angular【组件】
  • 公司网站做推广支出分录制作系部网站首页
  • adb disable-verity
  • 使用 Node.js 和 Express 构建 RESTful API
  • 局域网下怎么访问自己做的网站仿做网站可以整站下载器吧
  • 收录网站查询安徽省建设安全监督站的网站
  • 科技赋能农业现代化的破局之道
  • app网站建站系统策划方案珠海网站运营
  • 怎么选?时间序列数据预测-Transformer架构的模型和算法
  • 怎么使用创客贴网站做图学seo网站推广好吗
  • 做网站需要缴什么费用杭州cms建站模板
  • Python快速入门专业版(五十四):爬虫基石:HTTP协议全解析(从请求到响应,附Socket模拟请求)
  • 综合案例:Python 数据处理——从Excel文件到数据分析
  • Java基础——常用API2
  • 自己做的网站能上传到凡科吗网站站点多少钱
  • 手机如何网站成都哪里好玩
  • huggingface下载相关
  • rollup == JavaScript 打包器
  • ROS2 Windows安装
  • 四川省建设厅招标网站网站与网页 主页的概念及它们的区别
  • Unity编辑器扩展入门篇 - Unity Inspector自定义脚本菜单
  • Redis(一)——数据类型一