【C++练级之路】【Lv.19】【STL】unordered_set类和unordered_map类的模拟实现



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、哈希表(改造版)
    • 1.1 结点
    • 1.2 迭代器
      • 1.2.1 operator++
    • 1.3 本体
      • 1.3.1 成员变量和默认成员函数
      • 1.3.2 begin和end
      • 1.3.3 Find
      • 1.3.4 Insert
      • 1.3.5 Erase
  • 二、unordered_set
    • 2.1 成员变量与仿函数
    • 2.2 begin和end
    • 2.3 find
    • 2.4 insert
    • 2.5 erase
  • 三、unordered_map
    • 3.1 成员变量与仿函数
    • 3.2 begin和end
    • 3.3 find
    • 3.4 insert
    • 3.5 operator[ ]
    • 3.6 erase

引言

STL库中,set类和map类都是红黑树作为底层实现的,与之类似,unordered系列的unordered_set类和unordered_map类,都是通过哈希表作为底层来实现的。

一、哈希表(改造版)

1.1 结点

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

细节:

  • 数据类型改为T,因为要同时适用unordered_set(存储键值)和unordered_map(存储键值对)

1.2 迭代器

改造后的哈希表,最重要的功能之一就是支持单向迭代器

template<class K, class T, class KeyOfT, class Hash>
class HashTable;//前置声明template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct HashIterator
{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> Ht;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;Node* _node;const Ht* _ht;HashIterator(Node* node, const Ht* ht): _node(node), _ht(ht){}HashIterator(const Iterator& it): _node(it._node), _ht(it._ht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}
};

细节:

  1. 一些基本的迭代器范式操作已经给出,重点的++操作后面详细实现
  2. 增加_ht成员变量,原因:当一条单链表走到空,则需要走到下一个哈希桶的位置,需要哈希表的地址
  3. 这里存在相互引用的问题,所以前置声明哈希表
  4. const修饰_ht,使const迭代器能够被构造
  5. 迭代器的拷贝构造函数有两个用途:
    • 以普通迭代器拷贝出普通迭代器(普通迭代器调用时)
    • 以普通迭代器拷贝出const迭代器(const迭代器调用时)

1.2.1 operator++

Self& operator++()
{if (_node->_next){_node = _node->_next;}else{int flag = 0;size_t hashi = Hash()(KeyOfT()(_node->_data)) % _ht->_tables.size();for (size_t i = hashi + 1; i < _ht->_tables.size(); ++i){if (_ht->_tables[i]){_node = _ht->_tables[i];flag = 1;break;}}if (!flag){_node = nullptr;}}return *this;
}Self operator++(int)
{Self tmp = *this;++*this;return tmp;
}

细节:

  1. 前置++的思路:
    • 下一个结点不为空,则跳到下一位
    • 下一个结点为空,则先取模算出哈希地址,再往后探测不为空的哈希桶
  2. 后置++:复用前置++,返回临时对象

1.3 本体

1.3.1 成员变量和默认成员函数

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 HashIterator;
protected:typedef HashNode<T> Node;
public:HashTable(){_tables.resize(10);}~HashTable(){for (auto& cur : _tables){while (cur){Node* del = cur;cur = cur->_next;delete del;}}}
protected:vector<Node*> _tables;size_t _n = 0;//有效数据个数
};

细节:

  1. 将迭代器声明为友元,使迭代器内部可操作_tables
  2. 第三个模板参数为KeyOfT(仿函数类型),用于获取不同数据T的键值key来进行比较
  3. 第四个模板参数为Hash(仿函数类型),用于将不同类型key转换为整型来进行取模

1.3.2 begin和end

typedef HashIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
typedef HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;iterator begin()
{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return iterator(_tables[i], this);}}return iterator(nullptr, this);
}const_iterator begin() const
{for (size_t i = 0; i < _tables.size(); ++i){if (_tables[i]){return const_iterator(_tables[i], this);}}return const_iterator(nullptr, this);
}iterator end()
{return iterator(nullptr, this);
}const_iterator end() const
{return const_iterator(nullptr, this);
}

细节:

  1. begin返回最开始不为空的哈希桶的迭代器,end返回空迭代器
  2. 构造迭代器需要传入哈希表本身的地址,这里直接传this指针即可

1.3.3 Find

iterator Find(const K& key)
{size_t hashi = Hash()(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key){return iterator(cur, this);}cur = cur->_next;}return iterator(nullptr, this);
}

细节:

  1. 返回迭代器
  2. 运用两个仿函数,Hash转整型,KeyOfT获取键值

1.3.4 Insert

pair<iterator, bool> Insert(const T& data)
{KeyOfT kot;iterator it = Find(kot(data));if (it._node)//保持key唯一{return make_pair(it, false);}Hash hash;if (_n == _tables.size())//负载因子为1时,扩容{size_t newsize = _tables.size() * 2;vector<Node*> newtables(newsize);for (auto& cur : _tables){while (cur){Node* next = cur->_next;//将旧表结点重新映射到新表上size_t hashi = hash(kot(cur->_data)) % newsize;cur->_next = newtables[hashi];newtables[hashi] = cur;//跳回旧表的下一结点cur = next;}}_tables.swap(newtables);}size_t hashi = hash(kot(data)) % _tables.size();Node* newnode = new Node(data);//头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(iterator(newnode, this), true);
}

细节:

  1. 返回pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用两个仿函数,Hash转整型,KeyOfT获取键值

1.3.5 Erase

bool Erase(const K& key)
{size_t hashi = Hash()(key) % _tables.size();Node* prev = nullptr;Node* cur = _tables[hashi];while (cur){if (KeyOfT()(cur->_data) == key){if (prev == nullptr){_tables[hashi] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;
}

细节:运用两个仿函数,Hash转整型,KeyOfT获取键值

二、unordered_set

2.1 成员变量与仿函数

template<class K, class Hash = HashFunc<K>>
class unordered_set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};
public:
protected:HashTable<K, K, SetKeyOfT, Hash> _ht;
};

细节:

  1. unordered_set类仿函数,直接返回参数key
  2. 成员变量的第二个模板参数为K,第三个模板参数为SetKeyOfT
  3. 模板Hash可以根据特定需要而传手动实现的哈希化函数

2.2 begin和end

typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;iterator begin()
{return _ht.begin();
}const_iterator begin() const
{return _ht.begin();
}iterator end()
{return _ht.end();
}const_iterator end() const
{return _ht.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. unordered_set中存储的键值key均不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
  3. 由于unordered_set的普通迭代器也是哈希表的const迭代器,调用普通begin()时,便有从普通迭代器到const迭代器的转换,此时之前写的拷贝构造(以普通迭代器拷贝构造const迭代器)便派上用场了。

2.3 find

iterator find(const K& key)
{return _ht.Find(key);
}

2.4 insert

pair<iterator, bool> insert(const K& key)
{return _ht.Insert(key);
}

细节:

  1. 插入参数类型为K(键值)
  2. 此时也有从普通迭代器到const迭代器的转换

2.5 erase

bool erase(const K& key)
{return _ht.Erase(key);
}

三、unordered_map

3.1 成员变量与仿函数

template<class K, class V, class Hash = HashFunc<K>>
class unordered_map
{struct MapKeyOfT{const K& operator()(const pair<const K, V>& kv){return kv.first;}};
public:
protected:HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
};

细节:

  1. unordered_map类仿函数,返回参数pair的first
  2. 成员变量的第二个模板参数为pair,第三个模板参数为MapKeyOfT
  3. 模板Hash可以根据特定需要而传手动实现的哈希化函数

3.2 begin和end

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;iterator begin()
{return _ht.begin();
}const_iterator begin() const
{return _ht.begin();
}iterator end()
{return _ht.end();
}const_iterator end() const
{return _ht.end();
}

细节:

  1. 加上typename关键字,编译器才能识别类型
  2. unordered_map同样不允许修改key,故加上const修饰,但是允许修改存储的value,所以普通和const迭代器一一对应

3.3 find

iterator find(const K& key)
{return _ht.Find(key);
}

3.4 insert

pair<iterator, bool> insert(const pair<const K, V>& kv)
{return _ht.Insert(kv);
}

细节:插入参数类型为pair(键值对)

3.5 operator[ ]

unordered_map最好用的重载运算符[ ],我们肯定也要实现,平常插入和修改使用[ ]更加方便。

V& operator[](const K& key)
{pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;
}

细节:

  1. 插入成功便是插入,插入失败便是查找+修改
  2. 返回value的引用,可以直接插入或修改

3.6 erase

bool erase(const K& key)
{return _ht.Erase(key);
}

真诚点赞,手有余香

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

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

相关文章

MacOS下载和安装HomeBrew的详细教程

在MacOS上安装Homebrew的详细教程如下&#xff1a;&#xff08;参考官网&#xff1a;macOS&#xff08;或 Linux&#xff09;缺失的软件包的管理器 — Homebrew&#xff09; 步骤1&#xff1a;检查系统要求 确保你的MacOS版本至少为macOS Monterey (12) (or higher) 或更高版本…

HAL STM32主从定时器联级使用

HAL STM32主从定时器联级使用 具体介绍参考STM32参考手册 &#x1f33f;主从定时器联级&#xff1a;使用一个定时器作为另一个定时器的预分频器。 &#x1f341;时钟关系&#xff1a; &#x1f33f;TIM1 和TIM8 控制寄存器 2(TIMx_CR2)相关位&#xff1a; &#x1f516;主…

自动驾驶汽车关键技术_感知

自动驾驶汽车关键技术|感知 附赠自动驾驶学习资料和量产经验&#xff1a;链接 两套标准 分别由美国交通部下属的国家高速路安全管理局(NationalHighwayTraffic Safety Administration &#xff0c;NHSTA) 和国际汽车工程师协会&#xff08;Societyof Automotive Engineers&am…

【QT入门】 Qt代码创建布局综合运用:仿写腾讯会议登陆界面

往期回顾&#xff1a; 【QT入门】 Qt代码创建布局之水平布局、竖直布局详解-CSDN博客 【QT入门】 Qt代码创建布局之栅格布局详解-CSDN博客 【QT入门】 Qt代码创建布局之分裂器布局详解-CSDN博客 【QT入门】 Qt代码创建布局综合运用&#xff1a;仿写腾讯会议登陆界面 一、界面分…

设计模式之建造者模式:灵活可扩展的对象创建过程

目录 一、什么是建造者模式 二、建造者模式的应用场景 三、建造者模式的优缺点 3.1. 优点 3.2. 缺点 四、建造者模式示例 4.1. 问题描述 4.2. 问题分析 4.3. 代码实现 五、建造者模式的另一种实现方式 六、总结 一、什么是建造者模式 建造者模式&#xff08;Builder…

C++从入门到精通——类的定义及类的访问限定符和封装

类的定义及类的访问限定符和封装 前言一、类的定义类的两种定义方式成员变量命名规则的建议示例 二、类的访问限定符和封装访问限定符访问限定符说明C为什么要出现访问限定符例题 封装例题 前言 类的定义是面向对象编程中的基本概念&#xff0c;它描述了一类具有相同属性和方法…

Unity和Android的交互

Unity和Android的交互 一、前言二、Android导出jar/aar包到Unity2.1 版本说明2.2 拷贝Unity的classes.jar给Android工程2.2.1 classes.jar的位置2.2.2 Android Studio创建module2.2.3 拷贝classes.jar 到 Android工程并启用 2.3 编写Android工程代码2.3.1 创建 MainActivity2.…

springboot之mybatisPlus多表查询及分页查询

文章目录 一、多表查询二、mybatis-plus条件查询三、分页查询 一、多表查询 可能会用到的注解 这里的场景是&#xff0c;查询每个用户及其所有的订单。就是查询你的id号的同时&#xff0c;把你所有的历史订单信息都拉出来。 表结构这样 CREATE TABLE User ( id INT PRIMARY…

施耐德中高端PLC仿真器

参考文档&#xff1a;《Unity Pro PLC 仿真器》EIO0000001719.06 &#xff08;Control Expert 就是 Unity Pro 最新版本换了个名字&#xff0c;两者操作基本相同&#xff09; https://www.schneider-electric.cn/zh/download/document/EIO0000001719/ 1. 适用 PLC 这里使用的…

TiDB 组件 GC 原理及常见问题

本文详细介绍了 TiDB 的 Garbage Collection&#xff08;GC&#xff09;机制及其在 TiDB 组件中的实现原理和常见问题排查方法。 TiDB 底层使用单机存储引擎 RocksDB&#xff0c;并通过 MVCC 机制&#xff0c;基于 RocksDB 实现了分布式存储引擎 TiKV&#xff0c;以支持高可用分…

计算机网络——37认证

认证 目标&#xff1a;Bob需要Alice证明他的身份 Protocol ap1.0&#xff1a;Alice说"A am Alice" 可能出现的问题&#xff1a; 在网络上Bob看不到Alice&#xff0c;因此Trudy可以简单的声称他是Alice 认证&#xff1a;重新尝试 Protocol ap2.0&#xff1a;Alice…

阿里云4核8G服务器ECS通用算力型u1实例优惠价格

阿里云4核8G服务器优惠价格955元一年&#xff0c;配置为ECS通用算力型u1实例&#xff08;ecs.u1-c1m2.xlarge&#xff09;4核8G配置、1M到3M带宽可选、ESSD Entry系统盘20G到40G可选&#xff0c;CPU采用Intel(R) Xeon(R) Platinum处理器&#xff0c;阿里云活动链接 aliyunfuwuq…

批量导入svg文件作为图标使用(vue3)vite-plugin-svg-icons插件的具体应用

目录 需求svg使用简述插件使用简述实现安装插件1、配置vite.config.ts2、src/main.ts引入注册脚本3、写个icon组件4、使用组件 需求 在vue3项目中&#xff0c;需要批量导入某个文件夹内数量不确定的svg文件用来作为图标&#xff0c;开发完成后能够通过增减文件夹内的svg文件&a…

一文解析智慧城市,人工智能技术将成“智”理主要手段

长期以来&#xff0c;有关智慧城市的讨论主要围绕在技术进步方面&#xff0c;如自动化、人工智能、数据的公开以及将更多的传感器嵌入城市以使其更加智能化。实际上&#xff0c;智慧城市是一个关于未来的设想&#xff0c;其重要原因在于城市中存在各种基础设施、政治、地理、财…

测试框架pytest学习与实践

pytest是一个专业的测试框架&#xff0c;可以帮助我们对python项目进行测试&#xff0c;提高测试的效率。 pytest官网手册&#xff1a;pytest: helps you write better programs — pytest documentation 中文手册&#xff1a;Pytest 教程 入门学习 安装pytest pip install…

代码随想录算法训练营第二十五天| 216.组合总和III、17.电话号码的字母组合

系列文章目录 目录 系列文章目录216.组合总和III17.电话号码的字母组合回溯法 216.组合总和III 本题k相当于树的深度&#xff0c;9&#xff08;因为整个集合就是9个数&#xff09;就是树的宽度。 剪枝&#xff1a;①for循环的范围剪枝&#xff0c;i < 9 - (k - path.size()…

Mac资源库的东西可以删除吗?mac资源库在哪里打开 cleanmymacx是什么 cleanmymac免费下载

在使用Mac电脑的过程中&#xff0c;用户可能会遇到存储空间不足的问题。一种解决方法是清理不必要的文件&#xff0c;其中资源库&#xff08;Library&#xff09;文件夹是一个常被提及但又让人迷惑的目标。Mac资源库的东西可以删除吗&#xff1f;本文旨在解释Mac资源库的作用、…

卫星遥感影像如何选择合适的分辨率

​ 卫星遥感影像的分辨率是影响其应用效果的关键因素之一。分辨率越高&#xff0c;所获取的图像细节越丰富&#xff0c;能够更准确地反映地物的特征和变化。因此&#xff0c;在选择卫星遥感影像时&#xff0c;需要根据实际需求和数据可获取性来选择合适的分辨率。 一、分辨…

Python向带有SSL/TSL认证服务器发送网络请求小实践(附并发http请求实现asyncio+aiohttp)

1. 写在前面 最近工作中遇到这样的一个场景&#xff1a;给客户发送文件的时候&#xff0c;为保证整个过程中&#xff0c;文件不会被篡改&#xff0c;需要在发送文件之间&#xff0c; 对发送的文件进行签名&#xff0c; 而整个签名系统是另外一个团队做的&#xff0c; 提供了一…

AI大语言模型GPT —— R 生态环境领域数据统计分析

自2022年GPT&#xff08;Generative Pre-trained Transformer&#xff09;大语言模型的发布以来&#xff0c;它以其卓越的自然语言处理能力和广泛的应用潜力&#xff0c;在学术界和工业界掀起了一场革命。在短短一年多的时间里&#xff0c;GPT已经在多个领域展现出其独特的价值…