【C++】使用哈希表封装unordered_map与unordered_set

文章目录

  • 1. unordered系列关联式容器
    • 1.1 unordered_set
    • 1.2 unordered_map
  • 2. unordered_set/map的封装
    • 2.1 基本接口
    • 2.2 迭代器
      • 2.2.1 迭代器的结构
      • 2.2.2 set迭代器的封装
      • 2.2.3 map迭代器的封装
  • 3.完整代码
    • 3.1HashTable
    • 3.2unordered_set
    • 3.3unordered_map

在这里插入图片描述

1. unordered系列关联式容器

在C++98中,STL提供了底层为红黑树结构的一系列关联式容器,在查询时效率可达到 l o g 2 N log_2 N log2N,即最差情况下需要比较红黑树的高度次,当树中的节点非常多时,查询效率也不理想。最好的查询是,进行很少的比较次数就能够将元素找到,因此在C++11中,STL又提供了4个unordered系列的关联式容器,这四个容器与红黑树结构的关联式容器使用方式基本类似,只是其底层结构不同,本文中只对unordered_map和unordered_set进行介绍,

1.1 unordered_set

  1. unordered_set是以不特定顺序存储唯一元素的容器,并允许根据其值快速检索单个元素。
  2. 在unordered_set中,元素的值同时是其键,它唯一地标识它键是不可变的,因此,unordered_set中的元素在容器中不能被修改,但是它们可以入和删除。
  3. 在内部,unordered_set中的元素不按任何特定顺序排序,而是根据其哈希值放到桶中,以便直接通过其值快速访问各个元素(平均平均时间复杂度恒定)。
  4. unordered_set容器通过其键访问单个元素的速度比set容器更快,尽管它们在通过其元素子集进行范围迭代时通常效率较低。

1.2 unordered_map

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

2. unordered_set/map的封装

2.1 基本接口

上一篇文章我们模拟实现了哈希表,在这里我们直接对其进行改造,将其封装为unordered_set/map。

由于我们的unordered_map与unordered_set使用的是一个哈希表,所以我们首先对哈希表的模板参数进行改造。
由于map是k、v模型,set是k,所以在哈希表那一层我们统一处理成T。
在这里插入图片描述

在哈希表内部使用key进行比较时,需要使用一个函数获得map与set的key。

所以,在哈希表中所有涉及使用T类型的data计算位置与比较的地方都得改。
在这里插入图片描述

unordered_map与unordered_set框架对比
由于一般key都是不允许修改的,所以这里set传递的是const K;map传的是pair<const K,V>
在这里插入图片描述

2.2 迭代器

2.2.1 迭代器的结构

对于哈希表而言,迭代器++应该指向当前桶的下一个元素,当前桶走完了就应该到下一个桶。
那如何弄清下一个桶在什么位置呢?
迭代器内部除了要有一个节点的指针,还应该有一个表的指针,在该表中可以找到桶的位置
在这里插入图片描述

此时编译我们的代码,发现找不到node和table

在这里插入图片描述

Node和HashTable找不到是因为我们把它们的定义放在了HTIterator的后面,编译器只会向上找,所以我们可以把Node的定义放在它上面,但是HashTable的定义能放在它上面吗?

由于我们后期会在HashTable中使用HTIterator,它们两个是相互依赖的,谁定义在谁上面都不行。所以我们可以前置声明一下,同时注意声明与定义处的缺省参数不能同时有

在这里插入图片描述

  1. operator++

迭代器++时,要知道当前桶中还有没有元素,如果有元素,则指向下一个元素;如果没有元素,则指向下一个桶的第一个元素。

由于HtIterator内部要访问哈希表成员_table,但由于哈希表的成员_table是私有的,在外部无法访问。所以我们可将HTIterator设置为HahTable的友元类
在这里插入图片描述

Self& operator++(){if (_node->_next)//当前桶还有元素{_node = _node->_next;}else{KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pt->_table.size();hashi++;//指向下一个桶while (hashi < _pt->_table.size()){if (_pt->_table[hashi])//“下一个”桶有元素{break;}else{hashi++;}}if (hashi == _pt->_table.size())//若后面没有桶了{_node = nullptr;  //end()}else{_node = _pt->_table[hashi];}}return *this;}
  1. -> 、*、!=
		T& operator*(){return _node->_data;}T* operator->(){return &_node->_data;}bool operator !=(const Self& s){_node != s._node;}

2.2.2 set迭代器的封装

  1. 普通迭代器

先在哈希表内部封装迭代器

在这里插入图片描述

再封装set的迭代器

在这里插入图片描述

此时我们set的迭代器就可以跑起来了

在这里插入图片描述
由于我们对模板参数进行传递时,K都使用了const修饰,所以key是不允许修改的。
在这里插入图片描述
2. const迭代器

首先我们要明白,const迭代器是不允许修改的,无论是K还是V。所以我们只需对迭代器的 -> 与 * 操作做修改即可。

所以我们要给底层迭代器增加两个模板参数,使用普通迭代器时可以对V进行修改;使用const迭代器不允许修改V。

在这里插入图片描述
哈希表中的const迭代器

在这里插入图片描述

set的const迭代器

在这里插入图片描述

2.2.3 map迭代器的封装

由于我们已经实现了set,所有哈希表内迭代器的坑已经被我们跳过了,这里我们只需简单的封装map即可。

  1. 普通迭代器

在这里插入图片描述

在这里插入图片描述

  1. const迭代器

在这里插入图片描述

在这里插入图片描述

  1. operator[ ]

对于map[key]而言,如果key已经存在,则插入失败,并且返回key所对应位置的迭代器。如果key不存在,则将key插入,value为类型的默认值,并返回其迭代器。

所以我们要改造find、insert函数,使其返回一个pair<iterator,bool>
在这里插入图片描述

map中operator的实现以及find和insert的更改

在这里插入图片描述
在这里插入图片描述

由于哈希表中的find与insert已经更改,所以我们set中的也得改

在这里插入图片描述

更改后的find与insert要这样使用:

在这里插入图片描述

3.完整代码

3.1HashTable

//原模板
template<class K>
struct HashFunc
{size_t operator()(const K& key){return key;}
};//特化
template<>
struct HashFunc<string>
{size_t operator()(const string& key){size_t sum = 0;for (auto& e : key){sum *= 31;//这里使用了直接地址法,避免字符串的key计算后相同sum += e;}return sum;}
};namespace hash_bucket
{template<class T>struct HashNode//节点{T _data;//数据域HashNode<T>* _next;//指针域HashNode(const T& data):_data(data), _next(nullptr){}};//前置声明template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>class HashTable;//迭代器template<class K, class T, class Ref,class Ptr,class KeyOfT, class Hash = HashFunc<K>>struct HTIterator{typedef HTIterator<K, T,Ref,Ptr, KeyOfT, Hash> Self;//重命名typedef HashNode<T> Node;HashNode<T>* _node;//指向节点的指针const HashTable<K, T, KeyOfT, Hash>* _pt;//哈希表指针HTIterator( Node* node, const HashTable<K,T,KeyOfT,Hash>* table):_node(node),_pt(table){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){if (_node->_next)//当前桶还有元素{_node = _node->_next;}else{KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _pt->_table.size();hashi++;//指向下一个桶while (hashi < _pt->_table.size()){if (_pt->_table[hashi])//“下一个”桶有元素{break;}else{hashi++;}}if (hashi == _pt->_table.size())//若后面没有桶了{_node = nullptr;  //end()}else{_node = _pt->_table[hashi];}}return *this;}bool operator !=(const Self& s){return _node != s._node;}};//哈希表template<class K, class T,class KeyOfT,class Hash>class HashTable{template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hash>friend struct HTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T,T&,T*, KeyOfT, Hash> Iterator;//普通迭代器typedef HTIterator<K, T,const T&,const T*, KeyOfT, Hash> ConstIterator;//const迭代器Iterator begin(){if (_n == 0)//没有元素return end();else{for (size_t i = 0; i < _table.size(); i++){if (_table[i])//找到第一个桶{return Iterator(_table[i], this);//使用当前节点和桶构造一个迭代器返回}}}return end();}Iterator end(){return Iterator(nullptr, this);}ConstIterator cbegin()const {if (_n == 0)//没有元素return cend();else{for (size_t i = 0; i < _table.size(); i++){if (_table[i])//找到第一个桶{return ConstIterator(_table[i], this);//使用当前节点和桶构造一个迭代器返回}}}return cend();}ConstIterator cend() const{return ConstIterator(nullptr, this);}HashTable(size_t N = 10){_table.resize(N,nullptr);}~HashTable(){for (size_t i = 0; i < _table.size(); i++){Node* cur = _table[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_table[i] = nullptr;}}pair<Iterator,bool> insert(const T& data){Hash hs;KeyOfT kot;Iterator it = find(kot(data));//如果已经存在if ( it!= end()){return make_pair(it, false);}size_t size = _table.size();//检查扩容if (_n == size)//节点个数等于桶的数量时,进行扩容{//为了节省开销,不再重新开辟新节点,直接映射原来的节点,将原来的映射取消vector<Node*> newtable(size * 2, nullptr);size_t newsize = newtable.size();for (size_t i = 0; i < size; i++){Node* cur = _table[i];while (cur){size_t hashi = hs(kot(cur->_data)) % newsize;//元素对应的新表中的位置Node* next = cur->_next;//记录当前桶的下一个元素//头插连接到新桶cur->_next = newtable[hashi];newtable[hashi] = cur;cur = next;}_table[i] = nullptr;}swap(_table, newtable);}size_t hashi = hs(kot(data)) % _table.size();//头插连接Node* newnode = new Node(data);newnode->_next = _table[hashi];_table[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);}Iterator find(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];while (cur){if (kot(cur->_data) == key)return Iterator(cur,this);cur = cur->_next;}return end();}bool erase(const K& key){Hash hs;KeyOfT kot;size_t hashi = hs(key) % _table.size();Node* cur = _table[hashi];Node* prev = nullptr;while (cur){if (kot(cur->_data) == key){if (prev == nullptr)//桶中只有一个元素{_table[hashi] = nullptr;}else{prev->_next = cur->_next;}delete cur;_n--;return true;}else{prev = cur;cur = cur->_next;}}return false;}private:vector<Node*> _table;size_t _n;};
}

3.2unordered_set

namespace my
{template<class K,class Hash = HashFunc<K>>class unordered_set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename hash_bucket::HashTable<K,const K,SetKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K,const K,SetKeyOfT, Hash>::ConstIterator const_iterator;const_iterator cbegin() const{return _ht.cbegin();}const_iterator cend() const{return _ht.cend();}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}pair<iterator,bool> insert(const K& key){return _ht.insert(key);}iterator find(const K& key){return _ht.find(key);}bool erase(const K& key){return _ht.erase(key);}private:hash_bucket::HashTable<K, const K,SetKeyOfT, Hash>  _ht;};
}

3.3unordered_map

namespace my
{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 hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT, Hash>::Iterator iterator;typedef typename hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT, Hash>::ConstIterator const_iterator;const_iterator cbegin()const{return _ht.cbegin();}const_iterator cend()const{return _ht.cend();}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}V& operator[](const K& key){pair<iterator, bool> ret = _ht.insert(make_pair(key, V()));//将其对应的值返回//如果插入前已经存在,则返回其值;否则则返回一个V()return ret.first->second;}pair<iterator,bool> insert(const pair<K,V>& kv){return _ht.insert(kv);}iterator find(const K& key){return _ht.find(key);}bool erase(const K& key){return _ht.erase(key);}private:hash_bucket::HashTable<K, pair<const K,V>, MapKeyOfT, Hash>  _ht;};
}

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

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

相关文章

Windows Redis启动方式及保持服务运行方法

1. Redis启动方法 1. cmd进入redis文件夹下&#xff0c;输入&#xff1a;redis-server.exe redis.windows.conf&#xff0c;出现如下界面启动成功。但此cmd窗口要一直保持打开状态&#xff0c;一旦关闭redis也就关闭了。要想cmd关闭&#xff0c;但redis处于打开状态&#xff…

医疗器械上市欧美,需要什么样的网络安全相关申报文件?

医疗器械在欧美上市时&#xff0c;需要提交的网络安全相关申报文件主要包括以下几个方面&#xff0c;这些要求基于欧美地区的法律法规和监管机构的指导文件。 一、美国FDA要求 1. 网络安全管理计划 内容&#xff1a;制造商需要提交一份网络安全管理计划&#xff0c;该计划应包含…

【人工智能】人工智能概论(一):人工智能基本概概念、学派、发展历程与新一代人工智能

文章目录 1. 人工智能的基本概念与定义2. 人工智能的主要学派及主旨思想2.1. 符号主义学派&#xff1a;AI源自数学逻辑2.2. 连接主义学派&#xff1a;AI源自仿生学2.3. 行为主义学派&#xff1a;AI源自控制论 3. 人工智能的起源及发展历程4. 驱动新一代人工智能快速发展的因素 …

【C语言】C语言期末突击/考研--导学篇

前言 我将把C语言的知识要点&#xff0c;学习收获以文章形式发表&#xff0c;由于我目前也还是一个菜鸟&#xff0c;难以避免错误和存在观点片面的部分&#xff0c;非常感谢读者指正&#xff01;希望能在这里与大家共同进步&#xff0c;早日成为大牛&#xff01;进入大厂&…

本地使用Git同步、配合Gitee同步至仓库并下拉到本地(亲手调试,全能跑通)

这几天在公司&#xff0c;同事都在使用Gitee上传项目&#xff0c;进行同步&#xff0c;我也进行了简单学习了解了一下版本控制软件Git&#xff0c;挺不错的&#xff0c;故写个笔记记录一下。 本篇博文主要涉及的内容&#xff1a; 1&#xff0c;本地写代码&#xff0c;通过Git同…

初阶数据结构1 算法复杂度

1.数据结构概念 数据结构(Data Structure)是计算机存储、组织数据的⽅式&#xff0c;指相互之间存在⼀种或多种特定关系的数 据元素的集合。没有⼀种单⼀的数据结构对所有⽤途都有⽤&#xff0c;所以我们要学各式各样的数据结构&#xff0c; 如&#xff1a;线性表、树、图、哈…

【C++】—— 类和对象(一)

【C】—— 类和对象&#xff08;一&#xff09; 1、类的定义1.1、类定义1.1.1、类定义格式1.1.2、成员变量的标识1.1.3、C 中的 s t r u c t struct struct1.1.4、C 中的内联函数1.1.5、总结 1.2、访问限定符1.3、类域 2、实例化2.1、实例化的概念2.2、对象大小2.2.1、对象的大…

lenovo联想ThinkBook 14 G4+ IAP/ARA(21CX,21D0)笔记本原装出厂Windows11系统预装OEM镜像下载

ThinkBook 14 G4 IAP【21CX】原厂系统Win11恢复安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1iY9BxidIbv4RnXKaqbydTA?pwd9wc6 提取码&#xff1a;9wc6 ThinkBook 14 G4 ARA【21D0】原厂系统Win11恢复安装包&#xff1a; 链接&#xff1a;https://pan.ba…

nest学习笔记(一)

介绍 nest是一个用于构建高效&#xff0c;可拓展的nodejs服务端应用程序的框架&#xff0c;它使用渐进式javascript&#xff0c;使用Typescript构建并且完全支持Typescript&#xff0c;而且运行开发者使用javascript编写代码&#xff0c;提供了OOP、FP、FRP nest的底层是基于…

Linux编程:使用python或者shell获取系统信息

0. 概要 在日常的系统管理和性能监控中&#xff0c;获取系统信息是一个非常常见的需求。 本文将介绍如何使用Python和Shell脚本来获取这些系统信息。 1. 使用Python获取系统信息 使用psutil库来获取系统的CPU、内存、磁盘和网络信息。 1.1 安装psutil库 首先&#xff0c;我…

盗梦空间续集(InceptionNeXt):使用Inception优化加速ConvNeXt实现ImageNet-1K的最佳精度

Abstract 灵感来自ViT的长距离建模能力&#xff0c;大核卷积最近被广泛研究和采用&#xff0c;以扩大感受野并提高模型性能&#xff0c;例如显著的工作ConvNeXt采用77深度卷积。虽然这种深度算子只消耗少量的FLOPs&#xff0c;但由于高内存访问成本&#xff0c;它在强大计算设…

java实战项目--拼图小游戏(附带全套源代码)

个人主页VON 所属专栏java实战项目游戏参考黑马程序员 一、效果展示 二、功能介绍 游戏中所有的图片以及代码均已打包&#xff0c;玩家直接安装游戏即可&#xff0c;不用idea也可以畅玩。 游戏功能比较单一&#xff0c;只有简单的拼图功能。 a&#xff1a;展示原图重新游戏&a…

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础

《汇编语言 基于x86处理器》- 读书笔记 - 第3章-汇编语言基础 3.1 基本语言元素3.1.1 第一个汇编语言程序常见汇编语言调用规范 3.1.2 整数常量&#xff08;基数、字面量&#xff09;3.1.3 整型常量表达式3.1.4 实数常量十进制实数十六进制实数&#xff08;编码实数&#xff09…

USB 2.0 协议专栏之 USB 2.0 概述(一)

前言&#xff1a;本篇博客为手把手教学的 USB 2.0 协议栈类精品博客&#xff0c;该专栏博客侧重针对 USB 2.0 协议进行讲解。Universal Serial Bus 作为如今最常见的通信接口&#xff0c;被广泛应用于&#xff1a;Keyboard、Mouse、Communication Device Class 和 Mass Storage…

“论企业集成架构设计及应用”写作框架,软考高级论文,系统架构设计师论文

原创范文 论企业集成架构设计及应用企业集成架构(Enterprise Integration Arhitecture&#xff0c;EIA) 是企业集成平台的核心&#xff0c;也是解决企业信息孤岛问题的关键。企业集成架构设计包括了企业信息、业务过程、应用系统集成架构的设计。实现企业集成的技术多种多样&a…

什么是 Windows 服务

什么是 Windows 服务 Windows 服务是 Windows 操作系统的核心。它们控制着操作系统的运行&#xff0c;定义了图形用户界面&#xff08;GUI&#xff09;与系统硬件之间的通信以及其他许多功能。没有这些服务&#xff0c;Windows 操作系统将无法正常运行。 对普通用户而言&…

【人工智能】人工智能概述(二)人工智能的关键技术

文章目录 一. 机器学习与深度学习1. 机器学习2. 深度学习 二. 计算机视觉1. 基本概念和分类2. 未来计算机视觉面临的主要挑战 三. 自然语言处理1. 基本概念与分类2. 自然语言处理面临的四大挑战 四. 知识图谱1. 基本概念2. 应用场景 五. SLAM技术1. 基本概念2. 主要分类 六. 人…

DSP教学实验箱_数字图像处理操作_案例分享:5-13 灰度图像二值化

一、实验目的 学习灰度图像二值化的原理&#xff0c;掌握图像的读取方法&#xff0c;并实现在LCD上显示二值化前后的图像。 二、实验原理 图像二值化 图像的二值化处理就是将图像上的点的灰度置为 0 或 255&#xff0c;也就是将整个图像呈现出明显的黑白效果。即将 256 个亮…

AJAX(1)——axios库的使用

什么是AJAX? AJAX是异步的JavaScript和XML。简单来说&#xff0c;就是使用XMLHttpRequest对象与服务器通信。它可以使用JSON,XML,HTML和text文本等格式发送和接收数据。AJAX最吸引人的就是它异步的特性&#xff0c;也就是说它可以在不重新刷新页面的情况下与服务器通信&#…

昇思25天学习打卡营第25天|LLM应用-基于MindNLP+MusicGen生成自己的个性化音乐

打卡 目录 打卡 应用任务简介 生成音乐 预训练权重模型下载 无提示生成 文本提示生成 音频提示生成 生成配置 应用任务简介 MusicGen 来自 Meta AI 的 Jade Copet 等人提出的基于单个语言模型&#xff08;LM&#xff09;的音乐生成模型&#xff0c;能够根据文本描述或…