【C++进阶】用哈希实现unordered_set和unordered_map的模拟

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:c++大冒险
 

总有光环在陨落,总有新星在闪烁


前言:

        之前我们学完红黑树后对他进行了改造,使之成为map和set的底层容器,今天我们则要把哈希表进行修改并以此为基础实现unordered_set和unordered_map

一.哈希桶(修改版)

1.1.节点

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

注意事项:

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

类比咱们用红黑树写map和set时的做法。

1.2.迭代器

哈希表的迭代器类型是单向迭代器,没有反向迭代器

1.2.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable;
template<class K,class T, class Ref,class Ptr,class KeyOfT, class HF>
struct HashIterator
{typedef HashNode<T> Node;typedef HashIterator<K, T, Ref, Ptr, KeyOfT, HF> self;typedef HashIterator<K, T, T&, T*, KeyOfT, HF> iterator;typedef HashTable<K, T, KeyOfT, HF> HT;Node* _node;HT* _ht;
};

注意事项:

  •  增加了_ht成员变量,因为在重载++时,当一条单链表走到空,则需要走到下一个哈希桶的位置,需要通过哈希表的vector成员找下一个位置 
  • 因为HashTable是在后面实现的,所以我们要先写一个声明

1.2.2简单函数实现

HashIterator(Node*&node=nullptr,Node*&ht=nullptr):_node(node),_ht(ht)
{}
HashIterator(const iterator&it):_node(it._node), _ht(it._ht)
{}
Ref& operator*()const
{return _node->_data;
}
Ptr& operator->()const
{return &(_node->_data);
}
bool operator==(const self&se)
{return _node == se._node;
}
bool operator!=(const self& se)
{return _node != se._node;
}

注意事项:

  • 拷贝构造函数可以以普通迭代器拷贝出普通迭代器(普通迭代器调用时)以及const迭代器(const迭代器调用时)

1.2.3operator++

self& operator++()
{Node* node = _node;if (node->_next){_node = node->_next;return *this;}else{KeyOfT kot;size_t hash = HF(kot(_node->_data)) % _ht->_tables.size()+1;for (hash; hash < _ht->_tables.size(); hash++){if (_ht->tables[hash]){_node = _ht->tables[hash];return *this;}}_node = nullptr;return *this;}
}
self operator++(int)
{self new_self=*this;(*this)++;return new_self;
}

思路:

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

1.3哈希桶本身

1.3.1成员变量

template<class K, class T, class KeyOfT, class HF>
class HashTable
{template<class K, class T, class Ref, class Ptr, class KeyOfT, class HF>friend struct HashIterator;typedef HashNode<T> Node;
public:
protected:vector<Node*> _tables;size_t _size = 0;//有效数据个数
};

注意事项:

  1. 迭代器要访问哈希桶的私有成员,所以要声明友元
  2. 模板参数KeyOfT是为了从类型T中取出键值
  3. 模板参数HF即是HashFunc,哈希函数,用于辅助键值转化为整型进行取模操作

1.3.2构造函数和析构函数

HashTable(size_t size = 10)
{_tables.resize(size);
}
~HashTable()
{for (auto hash_node : _tables){while (hash_node){Node* new_node = hash_node->_next;delete hash_node;hash_node = new_node;}}
}

1.3.3 begin和end 

typedef HashIterator< K,T, T&, T*,  KeyOfT,  HF> iterator;
typedef HashIterator< K, T, const T&, const T*,  KeyOfT,  HF> 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()
{return const_iterator(nullptr, this);
}

注意事项:

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

1.3.4Find函数 

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

注意事项:

  1. 返回值类型是迭代器
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.5   Insert函数 

	pair<iterator,bool> Insert(T& data)
{KeyOfT kot;HF hf;iterator it = Find(kot(data));if (it._node){return make_pair(it, false);}if (_size == _tables.size()){vector<Node*> new_tables(_size * 2);for (auto node : _tables){while (node){Node* next = node->_next;size_t hash = hf(kot(node->_data)) % new_tables.size();node->_next = new_tables[hash];new_tables[hash] = node;node = next;}}_tables.swap(new_tables);}size_t hash = hf(kot(data)) % _tables.size();Node* cur = _tables[hash];Node* p(data);p->_next = cur;_tables[hash] = p;_size++;return make_pair(iterator(p,this),true);
}

注意事项:

  1. 返回值类型是pair,第一个参数为迭代器,第二个参数为布尔值(记录是否插入成功)
  2. 运用两个仿函数,,kot负责获取存储的键值,hf作为哈希函数把键值key转整型

1.3.6Erase函数 

	bool Erase(const K& key)
{HF hf;KeyOfT kot;size_t hash = hf(key) % _tables.size();Node* cur = _tables[hash];Node* pre = nullptr;while (cur){if (kot(cur->data) == key)break;pre = cur;cur = cur->_next;}if (cur == nullptr)return false;_size--;if (pre == nullptr)_tables[hash] = cur->_next;elsepre->_next = cur->_next;delete cur;return true;
}

二、unordered_set

2.1成员变量及仿函数 

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

注意事项:

  •  1.这里的数据存储类型就是键值Key本身,所以SetKeyOfT直接返回key就行
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

2.2 begin和end 

typedef typename HashTable<K, K, SetKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, K, SetKeyOfT, HF>::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();
}

注意事项:

  • 在C++中,编译器默认iterator为变量名,如果作为类型名,需要在前面加上typename加上typename关键字。
  • unordered_set中存储的键值key不允许修改,所以其普通迭代器和const迭代器均为哈希表的const迭代器
  • 我们称set的begin为A,哈希的begin为B,A的实现是对B的调用,在A的参数是普通迭代器时,B也是普通迭代器,B的返回值也是普通迭代器,但A的返回值是const迭代器,所以这里需要用普通迭代器创建一个const迭代器的临时变量,这就用到之前写的拷贝构造函数了。

2.3Find 

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

注意事项: 

  •  此时也有从普通迭代器到const迭代器的转换 

2.4Insert

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

注意事项: 

  1. 函数形参类型是K
  2. 此时也有从普通迭代器到const迭代器的转换

 2.5Erase

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

三、unordered_map

unordered_map和unordered_set的实现有众多相似之处,

3.1 成员变量与仿函数

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

注意事项:

  • 1.这里节点存的数据类型是pair<K,V>,我们的MapKeyOfT作用是返回其中的键值key 
  • 2.哈希函数不是固定的,可以根据需求自己进行实现并传到给定模板参数中

3.2 begin和end

typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::iterator iterator;
typedef typename HashTable<K, pair<K,V>, MapKeyOfT, HF>::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的pair中的K都要加上const修饰,但是允许修改存储的data,所以普通和const迭代器一一对应(好好想想这点map和set的处理差异)

3.3 find

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

3.4Insert

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

注意事项:

  • 形参类型是键值对而不是键值

3.5 operator[ ]

V& operator[](const& K key)
{return (*(_ht.Insert(make_pair(key, V()))).first).second;
}
//或者你也可以这么写
V& operator[](const K& key)
{pair<iterator, bool> cur = insert(make_pair(key, V()));return cur.first->second;
}

注意事项:

  1.  利用operator[]进行插入和修改操作是很方便的,所以要学会灵活运用哦 
  2. 返回值是value的引用,我们可以直接进行修改 

3.6 Erase 

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

 


 如果你对该系列文章有兴趣的话不妨点个关注哦,我会持续更新相关博客的

🥰创作不易,你的支持对我最大的鼓励🥰

🪐~ 点赞收藏+关注 ~🪐


 

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

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

相关文章

【C++ STL算法】sort 排序

文章目录 【 1. 基本原理 】【 2. sort 的应用 】实例 - sort 函数实现 升序排序和降序排序 函数名用法sort (first, last)基于 快速排序&#xff0c;对容器或普通数组中 [ first, last ) 范围内的元素进行排序&#xff0c;默认进行升序排序&#xff08;从小到大&#xff09;。…

2024年面试AI编译器岗经验总结

面试经历: 面试中必备的知识: 1.用C++实现一个卷积 (图解)一步一步使用CPP实现深度学习中的卷积 - GiantPandaCVGiantPandaCVhttp://giantpandacv.com/academic/%E7%AE%97%E6%B3%95%E7%A7%91%E6%99%AE/%E5%B0%BD%E8%A7%88%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E…

git 常用命令和使用方法

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

2014最新AIGC创作系统ChatGPT网站源码+AI绘画网站源码+GPT4-All联网搜索模型

一、文章前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

[C++][算法基础]字符串哈希(哈希表)

给定一个长度为 n 的字符串&#xff0c;再给定 m 个询问&#xff0c;每个询问包含四个整数 l1,r1,l2,r2&#xff0c;请你判断 [l1,r1] 和 [l2,r2] 这两个区间所包含的字符串子串是否完全相同。 字符串中只包含大小写英文字母和数字。 输入格式 第一行包含整数 n 和 m&#x…

HarmonyOS 应用开发-边缓存边播放案例

介绍 OhosVideoCache是一个支持边播放边缓存的库&#xff0c;只需要将音视频的url传递给OhosVideoCache处理之后再设置给播放器&#xff0c; OhosVideoCache就可以一边下载音视频数据并保存在本地&#xff0c;一边读取本地缓存返回给播放器&#xff0c;使用者无需进行其他操作…

Android Telephony框架

目录 一、简介二、应用层(Application)三、框架层(Framework)四、本地 RIL 层(RIL)五、驱动层(Modem)六、整体框架 一、简介 无论手机发展到如何智能的程度&#xff0c;最关键和重要的功能仍然是通讯&#xff0c;具体来说就是打电话、发短信、上网功能的使用。而整个 Android …

Centos 7 安装通过yum安装google浏览器

在CentOS 7上使用yum安装Google Chrome浏览器稍微复杂一些&#xff0c;因为Chrome并不直接包含在默认的Yum仓库中。按照以下步骤来操作&#xff1a; 1、添加Google Chrome仓库 首先&#xff0c;您需要手动添加Google Chrome的Yum仓库。打开终端&#xff0c;并使用文本编辑器&a…

MySQL高可用搭建方案MHA

MHA架构介绍 MHA是Master High Availability的缩写&#xff0c;它是目前MySQL高可用方面的一个相对成熟的解决方案&#xff0c;其核心是使用perl语言编写的一组脚本&#xff0c;是一套优秀的作为MySQL高可用性环境下故障切换和主从提升的高可用软件。在MySQL故障切换过程中&am…

使用 AI 生成正则表达式,告别正则烦恼

如果你有处理正则表达式的需求&#xff0c;那么这个网站&#xff08;autoregex.xyz&#xff09;一定要收藏好。 可以根据文字描述生成正则表达式。 默认是从文字到正则&#xff0c;不用选择。 输入框中输入描述&#xff0c;点击 ”GO“ 按钮。 等待一会儿&#xff0c;即可生…

get请求搜索功能爬虫

<!--爬虫仅支持1.8版本的jdk--> <!-- 爬虫需要的依赖--> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.2</version> </dependency>…

mos管开关出现尖峰的原理? mos管开关的时候cs会出现尖峰,请问这是什么原因?

MOS管在开关过程中出现尖峰现象&#xff0c;通常是由于电路中的寄生参数和快速电压变化引起的。以下是一些导致尖峰出现的主要原因和原理&#xff1a; 寄生电容 在MOS管的源极&#xff08;S&#xff09;和漏极&#xff08;D&#xff09;之间存在寄生电容&#xff0c;这个电容在…

面试总结------2024/04/04---项目

1.面试官提问&#xff1a;你说你在项目中使用springsecurity jwt 实现了登录功能&#xff0c;能简单讲一下怎么实现的吗&#xff1f; 2.使用RabbitMQ实现订单超时取消功能 redis实现的劣势 订单状态定义 首先&#xff0c;我们需要定义订单的不同状态。在这个示例中&#xf…

【Java】单例模式

单例模式是面试中常考的设计模式之一 在面试中&#xff0c;面试官常常会要求写出两种类型的单例模式并解释原理 本文中&#xff0c;将从0到1的介绍单例模式究竟是什么 文章目录 ✍一、什么是设计模式&#xff1f;✍二、单例模式是什么&#xff1f;✍三、单例模式的类型**1.饿汉…

线上研讨会 | 新一代数字化技术赋能机器人及智能产线行业高质量发展

随着智能制造的快速推进&#xff0c;制造业转型升级到了关键阶段。越来越多的企业以数字化技术搭配智能机器人及智慧产线&#xff0c;主动实现数字化转型。达索系统3D体验平台是实现企业数字化转型的新一代数智化平台&#xff0c;基于型、数字驱动、数字化连续技术&#xff0c;…

【深度学习基础】

打基础日常记录 CNN基础知识1. 感知机2. DNN 深度神经网络&#xff08;全连接神经网络&#xff09;DNN 与感知机的区别DNN特点&#xff0c;全连接神经网络DNN前向传播和反向传播 3. CNN结构【提取特征分类】4. CNN应用于文本 RNN基础1. RNN的本质 词向量模型word2Vec1. 自然语言…

Selenium与Metamask钱包及DApp交互及验证码破解汇总

1.LavaMoat报错: selenium.common.exceptions.WebDriverException: Message: unknown error: Runtime.callFunctionOn threw exception: Error: LavaMoat...`报错原因: 根本原因是Metamask为了用户钱包安全,而将LavaMoat 设置为了全局不可用。 报错解析文章链接: https:/…

城市定量分析学习资料大数据 gis 空间句法 Python

城市定量分析学习资料大数据 gis 空间句法 Python

Ubuntu 20.04.06 PCL C++学习记录(十六)

[TOC]PCL中点云分割模块的学习 学习背景 参考书籍&#xff1a;《点云库PCL从入门到精通》以及官方代码PCL官方代码链接,&#xff0c;PCL版本为1.10.0&#xff0c;CMake版本为3.16 学习内容 用一组点云数据做简单的平面的分割 源代码及所用函数 源代码 #include<iostr…

IP地址到底有什么用

IP地址在计算机网络中的作用至关重要&#xff0c;它不仅是设备在网络中的唯一标识&#xff0c;更是实现网络通信、网络管理和安全的关键要素。下面&#xff0c;我们将从多个方面详细阐述IP地址的作用。 首先&#xff0c;IP地址作为设备的唯一标识&#xff0c;为网络通信提供了…