【STL库】哈希封装 unordered_map/unordered_set - 教程

news/2025/9/18 21:12:24/文章来源:https://www.cnblogs.com/lxjshuju/p/19099739

在这里插入图片描述

励志不掉头发的内向程序员:个人主页

 ✨️ 个人专栏: 《C++语言》《Linux学习》

偶尔悲伤,偶尔被幸福所完善


️博主简介:

在这里插入图片描述

文章目录

  • 前言
  • 一、源码即框架分析
  • 二、模拟实现 unordered_map 和 unordered_set
    • 2.1、实现出复用哈希表的框架,并支持 insert
    • 2.2、支持 iterator 的实现
      • (1)iterator 实现思路分析
    • 2.3、unordered_map 支持 [ ]
  • 三、unordered_map 和 unordered_set 代码实现
  • 总结


前言

我们在上一章节了解完我们的哈希表是怎么实现后,我们又有 map/set 封装的经验,那我们的 unordered_map/unordered_set 的封装就会显得相对简单了,和我们 map/set 是差不多的,主要难点可能也是在迭代器的实现上了,我们就一起来看看吧。

在这里插入图片描述


一、源码即框架分析

我们 unordered_map/unordered_set 的实现和 map/set 实现的结构其实是一样的,我们都是用一个结构复用同时实现的, map/set 复用的是红黑树,而 unordered_map/unordered_set 复用的则是哈希表了。

通过源码可以看到,结构上 hash_map 和 hash_set 跟 map 和 set 的完全类似,复用同一个 hashtable 实现 key 和 key/value 结构,hash_set 传给 hash_table 的是两个 key,hash_map 传给 hash_table 的是 pair<const key, value>
在这里插入图片描述
注意:
源码里面跟 map/set 源码类似,命名风格比较乱,这里比 map 和 set 还乱,hash_set 模板参数居然用的 Value 命名,hash_map 用的是 Key 和 T 命名,可见⼤佬有时写代码也不规范。下面我们模拟一份自己的出来,就按自己的风格⾛了。

二、模拟实现 unordered_map 和 unordered_set

2.1、实现出复用哈希表的框架,并支持 insert

和 map/set 的思路是一样的,创建一个模板,我们想要让哈希表是什么 key 还是 key/value,就传什么类型的即可。

// MyUnorderedSet.h
template<
class K
>
class unordered_set
{
struct SetKeyOfT
{
const K&
operator()(const K& key)
{
return key;
}
};
public:
bool insert(const K& key)
{
return _ht.Insert(key);
}
private:
hash_bucket::HashTable<K, K, SetKeyOfT> _ht;};
// MyUnorderedMap.h
template<
class K
, class V
>
class unordered_map
{
struct MapKeyOfT
{
const K&
operator()(const pair<K, V>& kv){return kv.first;}};public:bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}private:hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT> _ht;};
// HashTable.h
template<
class K
>
struct HashFunc
{
size_t operator()(const K& key)
{
return (size_t)key;
}
};
namespace hash_bucket
{
template<
class T
>
struct HashNode
{
T _data;
HashNode<T>* _next;HashNode(const T& data):_data(data), _next(nullptr){}};// 实现步骤:// 1、实现哈希表// 2、封装unordered_map和unordered_set的框架 解决KeyOfT// 3、iterator// 4、const_iterator// 5、key不⽀持修改的问题// 6、operator[]template<class K, class T, class KeyOfT>class HashTable{typedef HashNode<T> Node;inline unsigned long __stl_next_prime(unsigned long n){static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}public:HashTable(){_tables.resize(__stl_next_prime(_tables.size()), nullptr);}~HashTable(){// 依次把每个桶释放for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}bool Insert(const T& data){KeyOfT kot;if (Find(kot(data)))return false;size_t hashi = kot(data) % _tables.size();// 负载因⼦==1扩容if (_n == _tables.size()){vector<Node*>newtables(__stl_next_prime(_tables.size()), nullptr);for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 旧表中结点,挪动新表重新映射的位置size_t hashi = kot(cur->_data) % newtables.size();// 头插到新表cur->_next = newtables[hashi];newtables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newtables);}// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;}private:vector<Node*> _tables;// 指针数组size_t _n = 0;// 表中存储数据个数};}

2.2、支持 iterator 的实现

(1)iterator 实现思路分析

iterator 实现的大框架:
跟 list 的 iterator 思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现,迭代器像指针一样访问的行为,要注意的是哈希表的迭代器是单向迭代器。

template<
class K
, class T
, class Ptr
, class Ref
, class KeyOfT
, class Hash
>
struct HTIterator
{
typedef HashNode<T> Node;typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node), _pht(pht){}Ref operator*(){}Ptr operator->(){}bool operator!=(const Self& s){}Self&operator++(){}};

iterator 实现思路分析:

  • 这里的难点是 operator++ 的实现。iterator 中有一个指向结点的指针,如果当前桶下面还有结点,则结点的指针指向下一个结点即可。如果当前桶走完了,则需要想办法计算找到下一个桶。这里的难点反而是结构设计的问题,参考上面的源码,我们可以看到 iterator 中除了有结点的指针,还有哈希表对象的指针,这样当前桶走完了,要计算下一个桶就相对容易多了,用 key 值计算出当前桶位置,依次往后找下一个不为空的桶即可。
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
Self&
operator++()
{
if (_node->_next)
{
// 当前桶还有节点
_node = _node->_next;
}
else
{
// 当前桶⾛完了,找下⼀个不为空的桶
KeyOfT kot;
Hash hs;
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;// end()}else{_node = _pht->_tables[hashi];}}return *this;}
  • begin() 返回第一个桶中第一个节点指针构造的迭代器,这里 end() 返回迭代器可以用空表示。
typedef HTIterator<K, T, T*, T&
, KeyOfT, Hash> Iterator;
typedef HTIterator<K, T, const T*, const T&
, KeyOfT, Hash> ConstIterator;
Iterator Begin()
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return Iterator(cur, this);
}
}
return End();
}
Iterator End()
{
return Iterator(nullptr, this);
}
ConstIterator Begin() const
{
if (_n == 0)
return End();
for (size_t i = 0; i < _tables.size(); i++)
{
Node* cur = _tables[i];
if (cur)
{
return ConstIterator(cur, this);
}
}
return End();
}
ConstIterator End() const
{
return ConstIterator(nullptr, this);
}
  • unordered_set 的 iterator 也不支持修改,我们把 unordered_set 的第二个模板参数改成 const K 即可,
    HashTable<K, const K, SetKeyOfT, Hash> _ht
  • unordered_map 的 iterator 不支持修改 key 但是可以修改 value,我们把 unordered_map 的第二个模板参数 pair 的第一个参数改成 const K 即可, HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht
// MyUnorderedSet.h
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;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}// MyUnorderedMap.htypedef 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;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}

实现了以上的代码,其余的就非常简单啦。

Ref operator*()
{
return _node->_data;
}
Ptr operator->
()
{
return &_node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;
}

2.3、unordered_map 支持 [ ]

  • unordered_map 要支持 [ ] 主要需要修改 insert 返回值支持,修改 HashTable 中的 insert 返回值为 pair<Iterator, bool> Insert(const T& data)
  • 有了insert⽀持[]实现就很简单了
V&
operator[](const K& key)
{
pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}

三、unordered_map 和 unordered_set 代码实现

// MyUnorderedSet.h
namespace bit
{
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;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{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;};}// MyUnorderedMap.hnamespace bit{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;iterator begin(){return _ht.Begin();}iterator end(){return _ht.End();}const_iterator begin() const{return _ht.Begin();}const_iterator end() const{return _ht.End();}pair<iterator, bool>insert(const pair<K, V>& kv){return _ht.Insert(kv);}V&operator[](const K& key){pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}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;};}// HashTable.htemplate<class K>struct HashFunc{size_t operator()(const K& key){return (size_t)key;}};// 特化template<>struct HashFunc<string>{size_t operator()(const string& key){size_t hash = 0;for (auto e : key){hash *= 131;hash += e;}return hash;}};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>class HashTable;template<class K, class T, class Ptr, class Ref, class KeyOfT, class Hash>struct HTIterator{typedef HashNode<T> Node;typedef HTIterator<K, T, Ptr, Ref, KeyOfT, Hash> Self;Node* _node;const HashTable<K, T, KeyOfT, Hash>* _pht;HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht):_node(node), _pht(pht){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}Self&operator++(){if (_node->_next){// 当前桶还有节点_node = _node->_next;}else{// 当前桶⾛完了,找下⼀个不为空的桶KeyOfT kot;Hash hs;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;// end()}else{_node = _pht->_tables[hashi];}}return *this;}};template<class K, class T, class KeyOfT, class Hash>class HashTable{// 友元声明template<class K, class T, class Ptr, class Ref, 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;Iterator Begin(){if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return Iterator(cur, this);}}return End();}Iterator End(){return Iterator(nullptr, this);}ConstIterator Begin() const{if (_n == 0)return End();for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];if (cur){return ConstIterator(cur, this);}}return End();}ConstIterator End() const{return ConstIterator(nullptr, this);}inline unsigned long __stl_next_prime(unsigned long n){static const int __stl_num_primes = 28;static const unsigned long __stl_prime_list[__stl_num_primes] ={53, 97, 193, 389, 769,1543, 3079, 6151, 12289, 24593,49157, 98317, 196613, 393241, 786433,1572869, 3145739, 6291469, 12582917, 25165843,50331653, 100663319, 201326611, 402653189, 805306457,1610612741, 3221225473, 4294967291};const unsigned long* first = __stl_prime_list;const unsigned long* last = __stl_prime_list + __stl_num_primes;const unsigned long* pos = lower_bound(first, last, n);return pos == last ? *(last - 1) : *pos;}HashTable(){_tables.resize(__stl_next_prime(_tables.size()), nullptr);}~HashTable(){// 依次把每个桶释放for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;delete cur;cur = next;}_tables[i] = nullptr;}}pair<Iterator, bool>Insert(const T& data){KeyOfT kot;Iterator it = Find(kot(data));if (it != End())return make_pair(it, false);Hash hs;size_t hashi = hs(kot(data)) % _tables.size();// 负载因⼦==1扩容if (_n == _tables.size()){vector<Node*>newtables(__stl_next_prime(_tables.size() + 1), 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);}// 头插Node* newnode = new Node(data);newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return make_pair(Iterator(newnode, this), true);}Iterator Find(const K& key){KeyOfT kot;Hash hs;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();}bool Erase(const K& key){KeyOfT kot;Hash hs;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] = cur->_next;}else{prev->_next = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;}private:vector<Node*> _tables;// 指针数组size_t _n = 0;// 表中存储数据个数};}


总结

到此为止,我们以及学会了 C++ 的大部分的容器了,只剩下一些像位图等不算特别困难的容器我们到后面有机会再来细谈,下一章节开始我们就会来聊聊我们 C++ 的一次重大的更新,C++11 的一些非常使用的新特性,我们下一章节再见吧。

在这里插入图片描述

坚持到这里已经很厉害啦,辛苦啦
ʕ • ᴥ • ʔ
づ♡ど

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

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

相关文章

Docker 常用命令详解与参数说明 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

7zip压缩解压缩-测试CPU性能

前言全局说明在B站刷大佬视频的时候,看到UP在Debign linux 上用7z测试CPU性能,惊讶,7z还有这功能。 赶快打开手边的电脑,看看 Windows 上的 7z 有没有这功能,一用,果然有这个功能。 以前想知道CPU性能就只能装些…

交叉编译openharmony版本的gdb

1465 cd ../gmp-6.3.0/ 1466 ./configure --prefix=/system/ CC=aarch64-linux-gnu-gcc --build=`./config.guess` --target=aarch64-linux-gnu --host=aarch64-linux-gnu 1467 make 1468 …

高数

1 求 \(\lim_{n\to\infty}\left(1-\frac 1n\right)^{n^2}\) 解: 首先证明 \(\lim_{n\to\infty}\left(1-\frac 1n\right)^{n}=e^{-1}\)。 \[\begin{align*} \lim_{n\to\infty}\left(1-\frac 1n\right)^{n} &=\lim_…

06-排序操作

06-排序操作$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");介绍 排序操作很常用,比如查询学员成绩,按照成绩降序排列。排序的SQL语法: select .. from .…

P5666 [CSP-S2019] 树的重心

分为 \(x \ne rt\) 和 \(x = rt\) 两种情况计算. 对于第一种情况,不难发现我们合法的裁减下来的连通块大小是在一个区间范围之内的,于是 DFS 时用一棵树状数组修改即可(因为这个大小可能是子树大小可能是子树外大小,这…

Java运行机制

Java 程序运行机制 编译型(compile) 解释型 程序运行机制 ![机制图](C:\Users\asus\Desktop\图集\屏幕截图 2025-09-18 204707.png)

除自身以外数组的乘积-leetcode

题目描述 给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 …

【2022】SDRZ夏令营游记

为什么2022的游记会在2025年发? 因为感觉洛谷博客快扛不住了,决定开始搬运。今天是夏令营最后一天了,在机房里坐不住了,写篇游记来纪念一下。 day0: 这天是gryz65级三个校区的信竞同学第一次大会师。我成功与一区…

rapidXML解析xml文件

1.rapidXML介绍 RapidXML 是一个轻量级、高性能的 XML 解析库,以单头文件形式提供(rapidxml.hpp 及辅助头文件),适合在 C++ 中解析中小型 XML 文档。获取 RapidXML:从 官方网站 下载头文件(rapidxml.hpp、rapidx…

2025.9.17 - 呓语

今天在Ubuntu虚拟机上安装配置了Hadoop,Hbase和ZooKeeper下面开始idea连接虚拟机Hbase. 1、在Hbase里创建StudentAndCourse表: (1)启动Hadoop(2)启动Hbase(3)进入shell界面(4)创建表(5)插入数据(6)查看表数据2、id…

office2024免费永久激活版下载安装教程:含激活步骤 + 一键安装包下载

目录一、 Office 2024 专业增强版介绍二、Office 2024 专业增强版核心功能(每个组件都好用)1.Word 2024:写文档再也不用熬夜改格式2.Excel 2024:处理数据超省心三、Office 2024 专业增强版下载渠道(正规才安全,避…

大学不止GPA

🎊精彩预告~~~在这个各类奖项进行评比的时间点,我没得到很好的结果,没有多少经济实力的我,却花了2个月生活费的价钱,买下了大疆Action 5 Pro。📸 上了大学之后,感觉自己反而很少得到来自外界的肯定。我想,有…

大学目标

一、自我介绍:从好奇到尝试 哈喽~我是王星星,一名大数据技术专业的学生。和很多 “一开始就目标明确” 的同学不同,我更像个在代码世界里 “逛园子” 的人 —— 对技术充满好奇,但还在摸索最感兴趣的方向。不过,…

福昕PDF编辑器专业版破解 v2025 中文版安装使用教程

福昕PDF编辑器专业版破解 v2025 中文版安装使用教程福昕高级pdf编辑器介绍 可以对PDF文件进行多种 创建、转换、打印、编辑、注释、表单处理、签名、保护、协同合作 等工作,强大的编辑套件让本身不可以编辑的PDF格式文…

MySQL LIMIT 和 ORDER BY 优化详解

MySQL LIMIT 和 ORDER BY 优化详解MySQL LIMIT 子句 MySQL LIMIT 子句是控制 SELECT 语句返回行数的重要工具。通过指定从结果集中获取的最大行数,它可以让你处理数据子集,尤其是在涉及大表的情况下。该功能可提高查…

python 架构专业的技术50

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

[论文笔记/评估方法] RELIABLE AND DIVERSE EVALUATION OF LLM MEDICAL KNOWLEDGE MASTERY

RELIABLE AND DIVERSE EVALUATION OF LLM MEDICAL KNOWLEDGE MASTERY该文章于2025年发表在ICLR(CCF A),早在2024年9月发布在arxiv。 文章地址:Reliable and Diverse Evaluation of LLM Medical Knowledge Mastery …

本地VMware Workstation Pro的rhel-server-7.9-x86_64服务器配置本地源

1. 安装好VMware Workstation Pro以及rhel-server-7.9-x86_64-dvd.iso后 2. 先对VMware Workstation 进行虚拟机关机 3. 对虚拟机的CD/DVD(SATA) 勾选设备状态为启动时连接,以及连接中勾选使用ISO镜像文件,为本地的r…

SCPI 标准命令

SCPI 标准命令*IDN? 是 SCPI 标准命令之一,用来返回仪器的身份信息。 📌 背景SCPI(Standard Commands for Programmable Instruments,标准可编程仪器命令)是 1990 年制定的一套通用指令集,几乎所有可编程仪器(…