unordered_map/set(底层实现)——C++

目录

前言:

1.开散列

1. 开散列概念

2. 开散列实现

2.1哈希链表结构体的定义

2.2哈希表类即私有成员变量

2.3哈希表的初始化

2.4迭代器的实现

1.迭代器的结构

2.构造

3.*

4.->

5.++

6.!=

2.5begin和end

2.6插入

2.7Find查找

2.8erase删除

3.unordered_map的封装

4.unordered_set的封装

5.5. 开散列与闭散列比较


前言:

我建议大家看过我《哈希表的底层实现之闭散列(C++)》、《红黑树模拟实现STL中的map与set——C++》这两篇文章之后再来看这一篇,因为这一篇的一部分哈希实现和一部分unordered_map和unordered_set的实现原理是建立在这两篇的基础之上的。

1.开散列

1. 开散列概念

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

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素。

2. 开散列实现

2.1哈希链表结构体的定义

//哈希链表
template<class T>
struct HashNode
{T _data;HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}
};

跟闭散列不同,这回我们的结构体里就没有状态码的设置了,因为我们这回是以链表的方式进行哈希桶的实现来处理哈希冲突,所以我们需要next指针指向产生哈希冲突的元素。

2.2哈希表类即私有成员变量

template<class K,class T,class KeyofT,class Hash>
class Hashtable
{//typedef HashNode<T> Node;using Node = HashNode<T>;private:vector<Node*> _tables;size_t _n;
};

我们的私有成员变量就两个,一个是std库里的vector容器,它里面的元素就是我们的一个个链表;然后再来一个_n代表有效元素的个数。

大家可能发现了我们的重命名是using Node = HashNode<T>;这个写法是C++11的语法,我们后面会专门用一篇文章来讲解C++11的语法。

2.3哈希表的初始化

	//初始化Hashtable():_n(0){_tables.resize(10,nullptr);}

我们这里就不弄的那么复杂,我们就简单开一个10空间大小的vector就可以了。

2.4迭代器的实现

在讲哈希表的增删查改之前我们要先来讲一下迭代器,因为我们后续的实现是建立在迭代器之上的。

我们本篇采用内部类的方式来进行迭代器的思想,将迭代器包在哈希表实现的里面。

1.迭代器的结构

	//内部类实现迭代器template<class Ptr,class Ref>struct __HIterator{typedef HashNode<T> Node;typedef __HIterator<Ptr,Ref> Self;Node* _node;const Hashtable* _pht;};

这里Ptr和Ref的作用还是跟之前一样,分别代表数据的指针和引用。然后里面我们对哈希链表和迭代器本身进行重命名,我们还需要哈希链表类型的变量以及哈希表指针类型的变量,具体都有什么作用,在后续的讲解中我们就会知道了。

2.构造

//构造__HIterator(Node* node, const Hashtable* pht):_node(node),_pht(pht){}

构造我们就采用哈希链表的节点指针以及整个哈希表的指针来进行初始化。因为迭代器的操作实际上都是对哈希表进行操作,所以我们需要知道具体的哈希表是哪个,然后确定是哪个节点。

3.*

		//*Ref operator*(){return _node->_data;}

解引用就返回我们的数据就好了。

4.->

		//->Ptr operator->(){return &_node->_data;}

->的作用自然就是返回我们的数据的地址。

5.++

		//++Self& operator++(){if (_node->_next){_node = _node->_next;}else{Hash hs;KeyofT kot;size_t hashi = hs(kot(_node->_data)) % _pht->_tables.size();++hashi;while (hashi<_pht->_tables.size()){if (_pht->_tables[hashi])break;hashi++;}if (hashi == _pht->_tables.size()){_node = nullptr;}else{_node = _pht->_tables[hashi];}}return *this;}

++的操作就是为了让我们找到下一个元素。我们是以一个个链表的形式进行存储的,所以我们要先在本条链表里访问完,再进行其它链表的访问。因此,我们的第一步就是访问本条链表的next节点,如果next节点有数据,我们就返回访问到这个数据的迭代器,如果next节点的位置为NULL,我们就要计算当前节点的哈希值,计算出来后继续访问下一个哈希值,如果下一个哈希值所指向的位置是空的,那么我们就要将hash++,继续往下找直到不为空。倘若后续没有数据了,我们就将_node设为空,否则将数据赋值给_node。

6.!=

		//!=bool operator!=(const Self& s){return _node != s._node;}

!=操作就是把两迭代器的_node进行一下比对就好了。

2.5begin和end

	typedef __HIterator<T*, T&> iterator;typedef __HIterator<const T*, const T&> const_iterator;//beginiterator begin(){for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return iterator(_tables[i], this);}}return end();}//enditerator end(){return iterator(nullptr, this);}//const  beginconst_iterator begin() const{for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return const_iterator(_tables[i], this);}}return end();}//const  endconst_iterator end() const{return const_iterator(nullptr, this);}

我们先将我们所写的普通迭代器和const迭代器进行一下重命名,然后进行接下来的操作。

我们的begin()就是哈希值为0的元素,end()就是为空的元素。我们再分别为他们实现const修饰的版本。

2.6插入

    //插入pair<iterator,bool> Insert(const T& data){Hash hs;KeyofT kot;iterator it = Find(kot(data));if (it!=end()){return make_pair(it,false);}if (_n == _tables.size()){vector<Node*> newtables(_tables.size() * 2, nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;size_t hashi = hs(kot(cur->_data)) % newtables.size();cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newNode = new Node(data);//头插newNode->_next = _tables[hashi];_tables[hashi] = newNode;++_n;return make_pair(iterator(_tables[hashi], this), true);}

在解决完前置内容之后,我们就要来进行插入操作的讲解了。我们先来看插入操作的返回值类型,大家是不是感到很疑惑?为什么是pair类型的呢?我也不卖关子,这实际上是为了我们后续对其进行封装时能够更加方便而设计的。我们先来把当个功能的思路搞懂,等我后面封装的时候大家就很清楚了。

我们先用Find函数来查找一下这个元素是否存在,如果存在,我们直接返回,如果不存在,我们就可以进行后续的操作了。如果有效元素个数_n跟表的有效空间相等,我们就要进行扩容操作。我们先创个两倍大小的新表,然后将旧表的内容拷贝到新表中,同时不要忘记了我们拷贝到新表的时候要重新计算哈希值,因为我们的表的有效空间已经变了。

扩完容之后我们在进行插入,我们先计算要插入的数据的哈希值,然后以头插的方式插入到哈希表当中。将有效元素++就好了。

2.7Find查找

	//查找iterator Find(const K& key){Hash hs;KeyofT kot;size_t hashi = hs(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return end();}

查找就很简单了,我们只需要计算哈希值,然后循环查找就可以了。返回的类型为迭代器也是为了我们后续的封装。

2.8erase删除

	//删除bool Erase(const K& key){Hash hs;KeyofT kot;size_t hashi = hs(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){//删除的是第一个if (prev == nullptr){_tables[hashi] = nullptr;}else{prev->_next = cur->_next;}delete cur;_n--;return true;}prev = cur;cur = cur->_next;}return false;}

删除操作也不复杂,我们只需要多一个prev节点记录当前节点的上一个节点就好了,我们的思路是这样的:我们先计算哈希值,然后用cur去指向哈希值所在的位置,如果当前位置没有数据,我们直接返回false,如果有我们就要看看是否与要删除的key值相等,若相等我们就要判断prev是否为空,为空就说明它是当前哈希值的第一个元素,我们就直接将当前哈希值的链表设为空就行,如果不为空,我们就要用prev链接cur的下一个节点。删除成功后不要忘记将n--。

3.unordered_map的封装

	template<class K,class V,class Hash=HashFunc<K>>class unordered_map{struct MapkeyofT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename Hashtable<K, pair<const K, V>, MapkeyofT, Hash>::iterator iterator;typedef typename Hashtable<K, pair<const K, V>, MapkeyofT, Hash>::const_iterator const_iterator;//begin()iterator begin(){return _ht.begin();}//end()iterator end(){return _ht.end();}//begin() constconst_iterator begin() const{return _ht.begin();}//end() constconst_iterator end() const{return _ht.end();}//[]V& operator[](const K& key){pair<iterator, bool> ret = Insert(make_pair(key, V()));return ret.first->second;}//插入pair<iterator, bool> Insert(const pair<K,V>& kv){return _ht.Insert(kv);}private:Hashtable< K, pair<const K, V>, MapkeyofT, Hash> _ht;};

unordered_map跟我之前那篇文章里map的封装非常类似,我这里就说说【】重载吧,insert为什么返回值是这个类型我们是参照标准库来定义的。(如图所示)

insert函数这么设计可以让我们重载【】的时候更加方便,我们可以直接复用它。

在说明【】前我们要知道【】能做什么?很简单,就是根据我们的key来返回对应的值。

我们知道插入成功或失败(表里有同样的数据)的时候都会返回它这个键值对,所以我们可以里利用这一点直接复用我们的insert然后取出我们的值就可以了。

4.unordered_set的封装

	template<class K,class Hash=HashFunc<K>>class unordered_set{struct SetkeyofT{const K& operator()(const K& key){return key;}};public:typedef typename Hashtable<K,const K, SetkeyofT, Hash>::iterator iterator;typedef typename Hashtable<K,const K, SetkeyofT, Hash>::const_iterator const_iterator;//begin()iterator begin(){return _ht.begin();}//end()iterator end(){return _ht.end();}//begin() constconst_iterator begin() const{return _ht.begin();}//end() constconst_iterator end() const{return _ht.end();}//插入pair<iterator, bool> Insert(const K& key){return _ht.Insert(key);}//finditerator Find(const K& key){return _ht.Find(key);}//erasebool Erase(const K& key){return _ht.Erase(key);}private:Hashtable< K,const K, SetkeyofT, Hash> _ht;};

unordered_set的封装跟set也是非常相似,没有什么新鲜的东西,只要看过我 《红黑树模拟实现STL中的map与set——C++》这篇文章的同学看这里的代码就几乎没什么难度。

5.5. 开散列与闭散列比较

应用链地址法处理溢出,需要增设链接指针,似乎增加了存储开销。事实上: 由于开地址法必须保持大量的空闲空间以确保搜索效率,如二次探查法要求装载因子a ,而表项所占空间又比指针大的多,所以使用链地址法反而比开地址法节省存储空间。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

在vue中:style 的几种使用方式

在日常开发中:style的使用也是比较常见的&#xff1a; 亲测有效 1.最通用的写法 <p :style"{fontFamily:arr.conFontFamily,color:arr.conFontColor,backgroundColor:arr.conBgColor}">{{con.title}}</p> 2.三元表达式 <a :style"{height:…

Gitlab学习(006 gitlab操作)

尚硅谷2024最新Git企业实战教程&#xff0c;全方位学习git与gitlab 总时长 5:42:00 共40P 此文章包含第21p-第24p的内容 文章目录 git登录修改root密码 设置修改语言取消相对时间勾选 团队管理创建用户创建一个管理员登录管理员账号创建一个普通用户登录普通用户账号 群组管理…

第6天:趋势轮动策略开发(年化18.8%,大小盘轮动加择时)

原创内容第655篇&#xff0c;专注量化投资、个人成长与财富自由。 轮动策略是一种投资策略&#xff0c;它涉及在不同的资产类别、行业或市场之间进行切换&#xff0c;以捕捉市场机会并优化投资组合的表现。 这种策略的核心在于识别并利用不同资产或市场的相对强弱&#xff0c…

自然语言处理-基于注意力机制的文本匹配

背景&#xff1a; 任务三&#xff1a;基于注意力机制的文本匹配 输入两个句子判断&#xff0c;判断它们之间的关系。参考ESIM&#xff08;可以只用LSTM&#xff0c;忽略Tree-LSTM&#xff09;&#xff0c;用双向的注意力机制实现。 参考 《神经网络与深度学习》 第7章 Reaso…

clousx6整点报时指令怎么写?

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

大模型的实践应用30-大模型训练和推理中分布式核心技术的应用

大家好,我是微学AI,今天给大家介绍一下大模型的实践应用30-大模型训练和推理中分布式核心技术的应用。本文深入探讨了大模型训练和推理中分布式核心技术的应用。首先介绍了项目背景,阐述了大模型发展对高效技术的需求。接着详细讲解了分布式技术的原理,包括数据并行、模型并…

SAP-MM-变式的设置

1、报表变式 业务需求: 业务人员查询报表时有些值是需要经常输入的,能不能设置成默认值?能不能设置成每次进入报表不选择变式直接是默认值? 解决措施: 1、事物码:MB51 以MB51物料凭证查询为例,其他报表自行举一反三 2、设置变式 首先进入MB51入下图 上图是没有选…

ros2编译RTSP驱动打开网络摄像头

按照这个链接里面的方法即可实现如下效果。

consul服务注册发现与配置中心

目录 1 consul安装与运行 1.1 下载方式 1.2 安装 1.3 启动 1.4 访问方式 2 consul 实现服务注册与发现 2.1 引入 2.2 服务注册 2.3 服务发现 3 consul配置中心 3.1 基础配置 Eureka已经停止更新了&#xff0c;consul是独立且和微服务功能解耦的注册中心&#xff0c;…

Tomcat 后台弱⼝令部署war包

漏洞原理 在tomcat8环境下默认进⼊后台的密码为 tomcat/tomcat &#xff0c;未修改造成未授权即可进⼊后台&#xff0c;或者管理员把密码设置成弱⼝令。 影响版本 全版本(前提是⼈家存在弱⼝令) 环境搭建 8 cd vulhub-master/tomcat/tomcat8 docker-compose up -d 漏洞复…

Python基于flask框架的智能停车场车位系统 数据可视化分析系统fyfc81

目录 技术栈和环境说明解决的思路具体实现截图系统设计python语言django框架介绍flask框架介绍性能/安全/负载方面可行性分析论证python-flask核心代码部分展示python-django核心代码部分展示技术路线操作可行性详细视频演示源码获取 技术栈和环境说明 结合用户的使用需求&…

引领长期投资新篇章:价值增长与财务安全的双重保障

随着全球金融市场的不断演变&#xff0c;长期投资策略因其稳健性和对价值增长的显著推动作用而日益受到投资者的重视。在这一背景下&#xff0c;Zeal Digital Shares&#xff08;ZDS&#xff09;项目以其创新的数字股票产品&#xff0c;为全球投资者提供了一个全新的长期投资平…

flutter遇到问题及解决方案

目录 1、easy_refresh相关问题 2、 父子作用域关联问题 3. 刘海屏底部安全距离 4. 了解保证金弹窗 iOS端闪退 &#xff08;待优化&#xff09; 5. loading无法消失 6. dialog蒙版问题 7. 倒计时优化 8. scrollController.offset报错 9. 断点不走 10.我的出价报红 11…

大气网格化精细化监管监测系统

大气网格化精细化监管监测系统是一种先进的环境监测与管理手段&#xff0c;它通过安装传感器、监测设备等手段&#xff0c;对大气环境进行精细化监测和控制。以下是朗观视觉小编对该系统的详细介绍&#xff1a; 一、系统概述 大气网格化精细化监管监测系统利用网格化布点技术&…

linux入门到实操-9 linux文件操作命令:创建文件、复制文件或文件夹、删除和移动文件、多种查看文件的方法

教程来源&#xff1a;B站视频BV1WY4y1H7d3 3天搞定Linux&#xff0c;1天搞定Shell&#xff0c;清华学神带你通关_哔哩哔哩_bilibili 整理汇总的课程内容笔记和课程资料&#xff08;包含课程同版本linux系统文件等内容&#xff09;&#xff0c;供大家学习交流下载&#xff1a;…

Qt 构建版本

Qt提供了三种不同的构建版本&#xff1a;Debug版本&#xff08;调试版本&#xff09;、Release版本&#xff08;发布版本&#xff09;和Profile版本&#xff08;概述版本&#xff09;&#xff0c;每种版本都有其特定的用途和编译设置。 Debug版本&#xff08;调试版本&#x…

Highcharts甘特图基本用法(highcharts-gantt.js)

参考官方文档&#xff1a; https://www.highcharts.com/docs/gantt/getting-started-gantt https://www.highcharts.com/demo/gantt/project-management https://www.hcharts.cn/demo/gantt 链接在下面按需引入 https://code.highcharts.com/gantt/highcharts-gantt.js htt…

搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(三)-文档

文档 文档服务负责写入&#xff0c;包括批量&#xff1b;id获取文档&#xff1b;nested写入 写入文档 写入文档主要是构建IndexRequest&#xff0c;索引请求 Elasticsearch v8构建文档索引请求简单很多&#xff0c;可以直接接受Map数据 批量写入文档 批量操作可以融合增删改…

你必须要懂的网络安全知识

不管是网工还是运维&#xff0c;都应该对网络安全的重要性非常清楚&#xff0c;每一次数据泄露、每一次网络攻击&#xff0c;都可能给企业带来不可估量的损失。 从SQL注入到跨站脚本攻击&#xff08;XSS&#xff09;&#xff0c;从分布式拒绝服务攻击&#xff08;DDoS&#xf…

科斯托拉尼的投机智慧:洞察人性与市场预期——《大投机家》读后感

《大投机家》是安德烈科斯托拉尼对投机艺术的深入探讨&#xff0c;也是一部充满智慧的投资哲学书籍。在他看来&#xff0c;投机不仅仅是追逐利润的游戏&#xff0c;而是对人性、市场预期、信息捕捉与解读的一场深刻博弈。如何在瞬息万变的股市中立于不败之地&#xff1f;科斯托…