【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.mzph.cn/news/796775.shtml

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

相关文章

掌握 TypeScript 核心:从基本类型到面向对象编程,理论详解与Vue3实践运用

TypeScript 是一种开源的编程语言&#xff0c;由微软公司开发&#xff0c;于2012年10月首次公开发布。 TypeScript 是 JavaScript 的超集&#xff0c;这意味着任何合法的 JavaScript 代码都是有效的 TypeScript 代码。它在 JavaScript 的基础上添加了静态类型系统、类、接口、模…

【Linux】有关时间的命令(date、timedatectl)

专栏文章索引&#xff1a;Linux 有问题可私聊&#xff1a;QQ&#xff1a;3375119339 目录 一、data命令 1.介绍 2.常用参数 3.常用选项 二、timedatectl命令 1.介绍 2.常用子命令 一、data命令 1.介绍 date命令用于显示或设置系统的时间与日期&#xff0c;语法格式为&a…

Nacos 入门篇---客户端如何发起服务注册?怎么发送服务心跳的(二)

一、引言 上个章节我们简单学习和使用了下Nacos服务自动注册&#xff0c;本文就来分析下Nacos客户端自动注册服务是怎么实现的&#xff5e; 二、目录 目录 一、引言 三、Nacos 源码编译 1.1 拉取代码 1.2 运行起来 四、客户端使用版本选择 五、Nacos客户端项目启动为什么会…

java数据结构与算法刷题-----LeetCode415. 字符串相加

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 模拟小学加法运算 模拟小学加法运算 解题思路&#xff1a;时间复…

深入浅出 -- 系统架构之分布式集群的分类

一、单点故障问题 集群&#xff0c;相信诸位对这个概念并不陌生&#xff0c;集群已成为现时代中&#xff0c;保证服务高可用不可或缺的一种手段。 回想起初集中式部署的单体应用&#xff0c;因为只有一个节点&#xff0c;因此当该节点出现任意类型的故障&#xff08;网络、硬件…

MySQL复制拓扑4

文章目录 主要内容一.启用GUID并配置循环复制1.其中&#xff0c;UUID用来唯一标识每一个服务器&#xff0c;事务的编号记录了在该服务器上执行的事务的顺序。使用SELECT server_uuid\G命令可以查看服务器的UUID&#xff0c;sever1的UUID值显示如下&#xff1a;代码如下&#xf…

区域自动气象站讲解

TH-QC10当我们每天查看天气预报&#xff0c;安排出行计划&#xff0c;或是在户外活动时关注天气变化&#xff0c;很少有人会想到这一切背后默默付出的“英雄”——区域自动气象站。这些看似不起眼的气象监测设备&#xff0c;却在我们日常生活中扮演着至关重要的角色。今天&…

单链表经典oj题 (一) 简单

1.删除指定节点数据&#xff08;非尾节点&#xff09;&#xff0c;要求时间复杂度为O(1) . - 力扣&#xff08;LeetCode&#xff09; 在之前我们将单链表删除指定节点的操作是先遍历找到pos的前一个结点&#xff0c;然后再进行删除&#xff0c;但是现在要求再O(1)时间内完成&am…

Kubernetes有状态任务

有状态任务是指执行期间需要维护一定状态或数据的任务或工作。这些任务通常需要记录并维护数据、状态、上下文或进度信息&#xff0c;并且这些信息在任务执行期间保持持久。有状态任务的解决目标是确保任务在不同的环境、节点或时间点之间维持一致的状态和标识。这种任务通常需…

2-3 AUTOSAR ASW Runable可运行实体

返回总目录->返回总目录<- 目录 一、概述 二、RTE Event 一、概述 运行实体(Runnable Entity,RE)是一段可执行的代码,其包含实际实现的函数(具体的逻辑算法或者操作)。一个软件组件可以包含一个或者多个运行实体。 Runnable就是SWC中的函数,而在AutoSAR架构在被…

Windows Server 2012 R2安装远程桌面服务

文章目录 一、打开【服务器管理器】二、点击【添加角色和功能】三、点击【下一步】四、点击【下一步】五、点击【下一步】![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/05b61a830faf477e81f858ec00bbdfff.png)六、勾选【远程桌面服务】→点击【下一步】七、点击【…

sharo反序列化漏洞

启动docker 服务 sudo service docker start 打开靶场 sudo docker run -d -p 80:8080 medicean/vulapps:s_shiro_1 输入docker虚拟机地址打开靶机sharo框架 如何利用漏洞 打开工具目录在终端中打开 输入靶机地址 打开yaki监听端口可以设置为6666 返回工具填写靶机ip和端口 …

Windows应急响应

1.排查隐藏账号 查看注册表 找到攻击者用户目录文件 排查用户异常 eventvwr.msc 分析用户登录日志 排查可疑端口 排查可疑进程 检查启动项、计划任务和服务 查看系统补丁信息 安装火绒&#xff0c;在安全工具里有火绒剑 计划任务 使用D盾对主机进行检测&#xff0c;发现隐藏账户…

中非绿色能源合作走深走实

近日&#xff0c;第十六届非洲能源大会在南非立法首都开普敦举行&#xff0c;探讨实现非洲能源转型的可持续解决方案。近年来&#xff0c;中国与非洲国家不断加强绿色能源合作&#xff0c;促进双方优势资源互补&#xff0c;逐步探索合作共赢的绿色能源合作方案。 势头良好 近年…

【201】Java8读取JSON树形结构并插入到MySQL数据库表中

我写了一个 maven 项目的 Demo&#xff0c;用来演示 JAVA8 如何读取 JSON 文件树形结构&#xff0c;并将这种树形结构保存到 MySQL 中。 json文件 city.json {"name": "山东省","sub": [{"name": "青岛市","sub"…

什么是SYN攻击,有什么办法防御SYN攻击

自进入数字化互联网时代&#xff0c;网络技术给我们带来了许多服务&#xff0c;为人们的生活增添了许多便利。但同时&#xff0c;网络安全问题也日益凸显&#xff0c;其中DDoS攻击&#xff0c;即分布式拒绝服务攻击&#xff0c;已经成为一种常见的网络威胁。这种攻击方式通过控…

Python爬取公众号封面图(零基础也能看懂)

&#x1f4da;博客主页&#xff1a;knighthood2001 ✨公众号&#xff1a;认知up吧 &#xff08;目前正在带领大家一起提升认知&#xff0c;感兴趣可以来围观一下&#xff09; &#x1f383;知识星球&#xff1a;【认知up吧|成长|副业】介绍 ❤️感谢大家点赞&#x1f44d;&…

文献速递:深度学习胰腺癌诊断--深度学习算法用于从疾病轨迹预测胰腺癌风险

文献速递&#xff1a;深度学习胰腺癌诊断--深度学习算法用于从疾病轨迹预测胰腺癌风险 麦田医学 美好事物中转站 2024-04-02 14:36 Title 题目 A deep learning algorithm to predict risk of pancreatic cancer from disease trajectories 深度学习算法用于从疾病轨迹预测…

Redis 客户端

Redis 客户端 客户端-服务器结构 Redis 同 Mysql 一样&#xff0c;也是一个客户端-服务器结构的程序&#xff0c;结构如下图&#xff1a; 注&#xff1a;Redis 客户端和服务器可以在同一个主机上&#xff0c;也可以在不同主机上 Redis 客户端的多种形态 自带的命令行客户端&…

HTML:表单

案例&#xff1a; <!DOCTYPE html> <html> <head><meta charset"UTF-8"><title>报名表</title> </head> <body><form action"demo/welcome.php" method"post">名字&#xff1a;<inpu…