【C++进阶】用哈希表封装unordered_set和unordered_map

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、改造哈希表
      • 1.1 解决 k/v 参数冲突问题
      • 1.2 插入操作的改造
      • 1.3 查找操作的改造
      • 1.4 删除操作的改造
  • 二、迭代器
      • 2.1 封装正向迭代器
      • 2.2 const迭代器
      • 2.3 unordered_map 新增 operator[]
  • 三、优化:素数表
  • 四、源码

一、改造哈希表

1.1 解决 k/v 参数冲突问题

在模拟实现mapset时,我们知道set<K,K>模型的红黑树,map<K,pair>模型的红黑树,而真正决定树里存储什么,是由第二个模板参数决定的,这也就是为什么mapset可以共用一颗树。

unordered系列容器也是如此,其中unordered_set<K,K>模型的哈希表,unordered_map<K,pair>模型的哈希表

【Unordered_set.h】

在这里插入图片描述

【Unordered_map.h】

在这里插入图片描述

由于unordered系列的底层使用的也是同一个哈希表,因此,真正决定表里存储什么,也是依靠第二个模板参数决定。

【OpenHashTable.h】

在这里插入图片描述

1.2 插入操作的改造

首先我们来分析插入的模板参数应该是什么?对于unordered_set就是key;对于unordered_map则是pair。那么参数类型应该用第二个模板参数V接收。

在这里插入图片描述

但这里就遇到了一个尴尬的问题:对于unordered_map来说,kv.first就是key;而对于set来说,kv就是key

因此,解决方法和map/set一样:写一个仿函数。首先在unordered_mapunordered_set里面写两个内部类,这个内部类重载运算符()。对于unordered_map返回pairfirst,对于unordered_set返回它本身,也就是key

【Unordered_set.h】

在这里插入图片描述

【Unordered_map.h】

在这里插入图片描述

这下就可以对哈希表进行改造了

【OpenHashTable.h】

在这里插入图片描述

1.3 查找操作的改造

在这里插入图片描述

1.4 删除操作的改造

在这里插入图片描述

二、迭代器

2.1 封装正向迭代器

哈希表的正向迭代器和链表类似,实际上就是对哈希结点指针进行了封装

在这里插入图片描述

但是由于在实现++运算符重载时,可能在当前坑位就有非空结点,那么_node = _node->_next来遍历下一个非空结点,也有可能下一个就是空结点了,因此就需要在哈希表中去寻找下一个非空坑位,因此每一个正向迭代器中还都应该存储哈希表的地址(指针) 来帮助我们判断表中的下一个坑位是否为空

在这里插入图片描述

然后再在迭代器类__HTIterator中实现operator!=operator*operator->,那么遍历操作就已经完成一半了

在这里插入图片描述

接着在类HashTable中封装begin()end()

在这里插入图片描述

最后分别在Unordered_map.hUnordered_set.h中调用begin()end()

【Unordered_map.h】

在这里插入图片描述

【Unordered_set.h】

在这里插入图片描述

当运行时发现编译不通过!!

在这里插入图片描述

这里其实存在相互依赖的问题,也就是说这里存在相互使用

在这里插入图片描述

什么意思呢?假设是unordered_set使用哈希表迭代器,那么迭代器的定义要在哈希表之前,因为代码会向上搜索;而又因为迭代器类要用到哈希表,而代码向上搜索时没发现哈希表的定义,因此在迭代器类中typedef HashTable<K, V, GetKey, HashFunc> HashPtr其实是未定义的。

解决方式:在迭代器类前声明哈希表即可但是要注意声明格式:参数列表 + class类名

在这里插入图片描述

接下来运行看看结果:

在这里插入图片描述

可是又报错了,原因是在__HTIterator中,访问了类HashTable的私有成员_table(类外不允许访问私有成员)
在这里插入图片描述

解决方法:友元声明。迭代器是哈希表的友元

在这里插入图片描述

【测试结果】

在这里插入图片描述

这里我们对unordered_map遍历使用范围for,因为范围for的底层就是迭代器

在这里插入图片描述

2.2 const迭代器

但是以上代码还是不完美,不管是unordered_setunordered_map,都可以修改key,一旦修改了,那么就意味着这表已经乱了。

回想一下当时模拟实现map/set

在这里插入图片描述

因此,unordered系列也可以模仿以上操作:对迭代器类增加两个模板参数来控制迭代器的operator*operator->的返回值

首先修改迭代器类在哈希表的声明,并增加const版本的beginend接口供unordered系列容器调用:

在这里插入图片描述

当然了,咱们自定义类型的迭代器也得修改

在这里插入图片描述

【Unordered_Set.h】

在这里插入图片描述

先来简单测试一下:

在这里插入图片描述

接下来再来测试const迭代器遍历

在这里插入图片描述

通过排查,是哈希表封装的beginend出问题了

在这里插入图片描述

原因如下:

在这里插入图片描述

解决办法很简单,由于迭代器不会修改哈希表的内容,因此对哈希表指针用const修饰

在这里插入图片描述

【Unordered_Map.h】

在这里插入图片描述

2.3 unordered_map 新增 operator[]

在这里插入图片描述

operator[]原理是:通过调用insert来查找键值key来返回实值value的引用

  • 如果键值key已经在表里,那么就返回表里面key所在结点的迭代器
  • 如果键值key不在表里,那么就返回新插入key所在结点的迭代器

所以还得将insert的返回值再进一步完善

在这里插入图片描述

【OpenHashTable.h】

首先由于insert操作中有用到Find函数,因此要Find函数的返回值改为迭代器

在这里插入图片描述

接下来可以修改insert的返回值了

在这里插入图片描述

接着再重新修改unordered_mapunordered_set封装insert的返回值

【Unordered_map.h】

在这里插入图片描述

【Unordered_set.h】

在这里插入图片描述

当编译的时候发现还是不通过

在这里插入图片描述

这是当时我们封装mapset一样的问题:_ht是一个普通对象,去调用哈希表的insert,那么返回的是普通的迭代器;而在unordered_set中,迭代器都是const迭代器。

就这么说吧,以上代码在编译器眼中其实是这样的:

在这里插入图片描述

又由于pair是一个类模板,类模板实例化不同的模板参数就是不同的类型。例如定义vector<int> vivector<char> vc,将vc赋值给vi成立吗?显然不可能!

在这里插入图片描述

那么如何解决呢?可以想想不同类型的内置类型是如何相互转化的?

在这里插入图片描述
因此,可以对迭代器类写一个拷贝构造函数:将普通迭代器构造成const迭代器

这种list容器其实也有一样的玩法:

在这里插入图片描述

如上代码,普通对象l调用begin返回普通迭代器,然后再赋值普通迭代器合情合理,但是也可以赋值给const迭代器,按常理普通迭代器赋值给const迭代器是不允许的!如果允许将普通迭代器赋值给常量迭代器,那么就可能导致通过常量迭代器意外地修改数据,违反了常量迭代器的设计初衷。

因此,我们可以简单看看list的底层:

在这里插入图片描述

也就是说:list底层有一个拷贝构造函数,支持普通迭代器转化为const迭代器。

理论部分解释完了,那么现在就对__HTIterator类添加一个拷贝构造函数

【OpenHashTable.h】

在这里插入图片描述

  • 如果__HTIterator实例化成普通迭代器,重命名的iterator永远是一个普通迭代器,那么这个拷贝构造函数其实就是一个赋值。
  • 如果__HTIterator实例化成const迭代器,重命名的iterator永远是一个普通迭代器,那么iterator对象就会隐式转化为__HTIterator类型,也就实现了普通迭代器转化为const迭代器

自此,unordered_set最终insert接口如下:

在这里插入图片描述

解释:第一行_ht调用哈希表中的insert返回的是普通迭代器;第二行再通过pair的构造,对于firstsecond成员变量都会走初始化列表初始化,而这里的first恰好是一个自定义类型,那么自定义类型在初始化列表初始化必须调用它的构造函数,因此就可以实现普通迭代器转化为const迭代器。

最后就可以在unordered_map封装operator[]

在这里插入图片描述

三、优化:素数表

  • 使用除留余数法时,哈希表的大小最好是素数,这样能够减少哈希冲突产生的次数

SGI STL 中,哈希表 在扩容时就使用了这一技巧

在这里插入图片描述

简单来说,就是当我们扩容后,按照下一个素数值大小进行扩容

这些素数都是近似 2倍的大小关系,在确保不会频繁扩容的同时,尽可能减少哈希冲突

在这里插入图片描述

同样的,需要对插入函数进行改造

在这里插入图片描述

四、源码

Gitee仓库链接:点击跳转

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法:滑动窗口

文章目录 例题1&#xff1a;长度最小的子数组例题2&#xff1a;无重复字符的最长子串例题3&#xff1a;最大连续1的个数 III例题4&#xff1a;将 x 减到 0 的最小操作数例题5&#xff1a;水果成篮例题6&#xff1a;找到字符串中所有字母异位词例题7&#xff1a;串联所有单词的子…

网络工程技术-学习内容(非技术文)

公共基础双纹线的制作 认识网络环境 (1)ipv4 ipv4地址的构成&#xff0c;分类&#xff0c;子网刻分&#xff0c;超丽素合“ 交换机的基本配置telnet&#xff0c;ssh&#xff0c; web方式三种配置 van. sto.协议 VLAN 端口聚合 三层交换“ 路由器的基本配置《(端口 IP 地址配)《…

msvcp120.dll丢失的解决方法,教你快速解决msvcp120.dll问题

msvcp120.dll是一个在Windows操作系统中至关重要的系统文件&#xff0c;它属于Microsoft Visual C Redistributable Package的一部分。这个动态链接库文件&#xff08;DLL&#xff09;包含了运行某些应用程序所必需的C运行时库函数。当某个程序在运行过程中需要调用这些预先编译…

requests做接口测试

Requests 是用Python语言编写&#xff0c;基于 urllib&#xff0c;采用 Apache2 Licensed 开源协议的 HTTP 库。它比 urllib 更加方便&#xff0c;可以节约我们大量的工作&#xff0c;完全满足 HTTP 测试需求。Requests 的哲学是以 PEP 20 的习语为中心开发的&#xff0c;所以它…

rabbitmq基础(1)

1、背景 能实现消息队列的框架软件有很多&#xff0c;kafka、rabbitmq、RocketMq、activeMq、Redis&#xff08;非专业&#xff09;&#xff0c;各有各的特点和优缺点。但是之前的公司数据需求规模并非很大&#xff0c;所以采用rabbitmq作为消息队列。 2、rabbitMq的基础架构…

工业网关、物联网网关与PLC网关是什么?

网关是什么&#xff1f; 网关是一种用于连接不同网络的网络设备&#xff0c;其作用是实现网络之间的通信和数据交换。它负责将一个网络的数据转发到另一个网络&#xff0c;并且可以进行路由、转换和过滤等处理。通常用于连接局域网和广域网之间&#xff0c;可以是硬件设备或者软…

基于javaweb实现的学生选课系统

一、系统架构 前端&#xff1a;jsp | bootstrap | jquery | css 后端&#xff1a;spring | sprinmvc | mybatis 环境&#xff1a;jdk1.8 | mysql | maven | tomcat 二、代码及数据库 三、功能介绍 01. 登录页 02. 管理员-课程管理 03. 管理员-学生管理 04. 管…

网络编程:select、poll

.1、select完成TCP并发服务器 程序代码&#xff1a; #include <myhead.h> #define SER_IP "192.168.125.234" //服务端IP #define SER_PORT 8888 //服务端端口号int main(int argc, const char *argv[]) {//1.创建用于连接的套接字int sfds…

C#,电话数字键盘问题(Mobile Numeric Keypad problem)的算法与源代码

1 电话数字键盘问题 提供移动数字键盘。您只能按向上、向左、向右或向下至当前按钮的按钮。不允许您按最下面一行的角点按钮&#xff08;即.*和#&#xff09;。 移动键盘 给定一个数N&#xff0c;找出给定长度的可能数。 示例&#xff1a; 对于N1&#xff0c;可能的数字数为…

免费SSL证书有效期

免费SSL证书有效期现状 目前市场上主流的免费SSL证书提供商大多遵循行业规范&#xff0c;将免费证书的有效期设为3个月。这意味着每隔三个月&#xff0c;网站管理员必须重新申请、验证并安装新的SSL证书&#xff0c;以维持网站的HTTPS安全连接状态。这种做法已成为行业的常态&…

GEE入门篇|图像分类(一):非监督分类

在非监督分类中&#xff0c;我们有与监督分类相反的过程。 首先对光谱类进行分组&#xff0c;然后将其分类为簇。因此&#xff0c;在 Earth Engine 中&#xff0c;这些分类器是 ee.Clusterer 对象。 它们是“自学”算法&#xff0c;不使用一组标记的训练数据&#xff08;即它们…

C++复习笔记——泛型编程模板

01 模板 模板就是建立通用的模具&#xff0c;大大提高复用性&#xff1b; 02 函数模板 C另一种编程思想称为 泛型编程 &#xff0c;主要利用的技术就是模板 C 提供两种模板机制:函数模板和类模板 函数模板语法 函数模板作用&#xff1a; 建立一个通用函数&#xff0c;其函…

centos上部署k8s

环境准备 四台Linux服务器 主机名 IP 角色 k8s-master-94 192.168.0.94 master k8s-node1-95 192.168.0.95 node1 k8s-node2-96 192.168.0.96 node2 habor 192.168.0.77 镜像仓库 三台机器均执行以下命令&#xff1a; 查看centos版本 [rootlocalhost Work]# cat /…

2024腾讯Java面试题精选,教你抓住面试的重点

重要 大环境对于我们能力要求越来越高&#xff0c;医学专家又说今年冬天新冠肺炎将“席卷重来”。 如果疫情再次爆发&#xff0c;势必将再次影响企业的正常运作&#xff0c;一波裁员浪潮你又能否抗住&#xff1f; 不管如何&#xff0c;明年金三银四又是一波跳槽时机&#xf…

数字化时代的新里程碑:Web3的革命

在当今数字化时代&#xff0c;Web3正成为了一股强大的力量&#xff0c;重新定义了我们对互联网的认知。本文将深入探讨Web3的定义、特点&#xff0c;以及它对金融、供应链、社交媒体等领域的革命性影响&#xff0c;并展望Web3的未来发展。 1. Web3的定义与特点 Web3不仅是一种…

力扣206反转链表

206.反转链表 力扣题目链接(opens new window) 题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 1&#xff0c;双指针 2&#xff0c;递归。递归参考双指针更容易写&#xff0c; 为什么不用头插…

无代理方式实现VMware的迁移?详细解析

在当今数字化时代&#xff0c;数据的安全性和可用性对于企业至关重要。尤其是在VMware转变订阅策略后&#xff0c;原本永久订阅的产品转变为以年付费订阅的形式&#xff0c;导致客户不得不支付更多的费用&#xff0c;大幅增加了成本。同时&#xff0c;客户也对VMware未来发展前…

计算机图形学的作用

计算机图形学的作用 计算机图形学的作用1.创造数字世界2.物理世界的仿真模拟2.1 三维几何2.2 物理动态2.3 人体运动2.4 虚实融合 3.仿真模拟与智能应用 笔记来源&#xff1a;GAMES001-图形学中的数学 计算机图形学的作用 1.创造数字世界 计算机图形学创造数字世界 数字世界…

FEP容量瓶多应用于制药光电光伏行业

常用规格&#xff1a;25ml、50ml、100ml、250mlFEP容量瓶也叫特氟龙容量瓶&#xff0c;容量瓶是为配制一定物质的量浓度的溶液用的精确定容器皿&#xff0c;常和移液管配合使用。广泛用于ICP-MS、ICP-OES等痕量分析以及同位素分析等高端实验。地质、电子化学品、半导体分析测试…

鸿蒙Harmony应用开发—ArkTS声明式开发(基础手势:TapGesture)

支持单击、双击和多次点击事件的识别。 说明&#xff1a; 从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 接口 TapGesture(value?: { count?: number, fingers?: number }) 参数&#xff1a; 参数名称参数类型必填参…