【C++高阶】深度剖析:从零开始模拟实现 unordered 的奥秘

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:哈希底层
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

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

❀哈希

  • 📒1. 改造 HashTable
  • 📜2. HashTable的迭代器
    • 🌞迭代器基本设计
    • 🌈operator++()
    • 🌙begin()与end()
    • ⭐迭代器的构造
  • 📚3. Unordered_Set的模拟实现
    • 🧩Unordered_Set的基本设计
    • 🌸Unordered_Set测试
  • 📝4. Unordered_Map的模拟实现
    • 🧩Unordered_Map的基本设计
    • 🌸Unordered_Map测试
  • 📖5. 总结


前言:在C++标准库中,unordered_map和unordered_set作为高效的无序容器,以其基于哈希表的实现方式,为数据的快速查找、插入和删除提供了强有力的支持。这些容器通过哈希函数将元素映射到数组的索引上,从而实现了接近O(1)的平均时间复杂度操作,极大地提升了程序性能。然而,尽管它们的使用极为便捷,了解这些容器背后的工作原理和模拟实现过程,对于深入理解数据结构、算法设计以及优化程序性能都至关重要

本文旨在带领读者踏上一场探索之旅,从理论到实践,逐步揭开unordered_map和unordered_set的神秘面纱。我们将不仅探讨这些容器的基本概念和特性,详细阐述模拟实现的过程,更重要的是,通过模拟实现这两个容器,深入理解其内部机制,包括哈希表的构建、哈希函数的选择、冲突解决策略、动态扩容与再哈希等核心问题

本篇我们采用开散列的方式来模拟实现unordered,帮助读者掌握哈希的构建与使用,如果大家还不太了解哈希,建议先去阅读我的上一篇文章

让我们一起踏上学习的旅程,探索它带来的无尽可能!


📒1. 改造 HashTable

改造HashTable以适配unordered_mapunordered_set容器,主要涉及到如何根据这两种容器的特性来设计和实现HashTable节点的存储以及相应的操作。unordered_mapunordered_set的主要区别在于它们存储的元素类型:map存储键值对(key-value pairs),而set仅存储唯一的键值(通常是键本身作为值)。尽管如此,它们在底层数据结构(如HashTable)的实现上有很多相似之处


改造内容:

  • K:key的类型
  • T:如果是unordered_map,则为pair<K, V>; 如果是unordered_set,则为K
  • KeyOfT:通过T来获取key的一个仿函数类
  • HF: 哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将Key转换为整形数字才能
    取模
// unordered_set 与 unordered_set
// unordered_set -> HashTable<K, K>
// unordered_map -> HashTable<K, pair<K, V>>

HashTable的节点设计:

template<class T>
struct HashNode
{HashNode* _next; // 存放数据T _data; // 指向节点的下一个元素HashNode(const T& data):_data(data) ,_next(nullptr){}
};

而在上一篇文章中,我们有介绍了一个关于非整形求关键值的仿函数HashFunc,在模拟实现是可以直接加在模拟实现的类上。

// hash_bucket是一个命名空间
// KeyOfT 和 Hash则是简化特定运算的仿函数
hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;

适用于unordered的成员函数

代码示例(C++):

// 修改了返回类型
pair<iterator, bool> Insert(const T& data)
{Hash hf;KeyOfT kot;// 判断值是否存在iterator it = Find(kot(data));if (it != end()){// 插入失败返回已有节点的迭代器和false		return make_pair(it, false);}// 负载因子if (_n == _tables.size()){vector<Node*> newTables;newTables.resize(_tables.size() * 2, nullptr);// 遍历旧表for (size_t i = 0; i < _tables.size(); i++){Node* cur = _tables[i];while (cur){Node* next = cur->_next;// 挪动到新表size_t hashi = hf(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hf(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;// 插入成功返回新节点的迭代器和ture		return make_pair(iterator(newnode, this, hashi), true);
}// 返回类型修改成了迭代器
iterator Find(const K& key)
{Hash hf;KeyOfT kot;size_t hashi = hf(key) % _tables.size();Node* cur = _tables[hashi];while (cur){if (kot(cur->_data) == key){return iterator(cur, this, hashi);}cur = cur->_next;}return end();
}

📜2. HashTable的迭代器

🌞迭代器基本设计

代码示例(C++):

// 为了实现简单,在哈希桶的迭代器类中需要用到hashBucket本身,所以我们要进行一下前置声明,并且我们在 HashTable 中也要设置一个友元(friend)//前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;// 通过模板来达到const的迭代器的复用
template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
struct __HTIterator
{typedef HashNode<T> Node;typedef __HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;Node* _node;Ref operator*(){return _node->_data;}Ptr operator->(){return &(_node->_data);}bool operator!=(const Self& s){return _node != s._node;}
};template<class K, class T, class KeyOfT, class Hash = HashFunc<K>>
class HashTable
{typedef HashNode<T> Node;// 友元template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>friend struct __HTIterator;...... // 其他待实现的函数
}

🌈operator++()

因为哈希桶在底层是单链表结构,所以哈希桶的迭代器不需要operator–()操作,在operator++()的设计上,我们的问题是在走完这个桶之后,如何找到下一个桶,因此我们需要记录来方便寻找,于是我们引入了两个变量

// HashTable
const HashTable<K, T, KeyOfT, Hash>* _pht;
// 当前桶的位置
size_t _hashi;

代码示例(C++):


const HashTable<K, T, KeyOfT, Hash>* _pht;
size_t _hashi;Self& operator++()
{if (_node->_next){// 当前桶没走完,移动到下一个节点_node = _node->_next;}else{// 初步尝试方法// 当前桶走完了,走下一个桶/*Hash hf;KeyOfT kot;size_t hashi = hf(kot(_node->_data)) % _pht._tables.size();*/// 当前桶走完了,走下一个桶++_hashi;while (_hashi < _pht->_tables.size()) // 判断当前桶的位置是否合法{// 判断当前桶是否存在数据if (_pht->_tables[_hashi]){_node = _pht->_tables[_hashi];break;}++_hashi;}// 走完了if (_hashi == _pht->_tables.size()){_node = nullptr;}}return *this;
}

🌙begin()与end()

关于构建迭代器的begin()end()当我们模拟实现const版本时,又会遇到新的问题,const版本在调用构造时,调不动,因为我最开始实现的构造函数不是const版本,当const版本想要调用构造函数时,这时造成了权限的扩大,因此为了解决这个问题,我们重载了构造函数

代码示例(C++):

// 普通版本
typedef __HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
// const 版本
typedef __HTIterator<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, i);}}return end();
}iterator end()
{return iterator(nullptr, this, -1);
}const_iterator begin() const
{for (size_t i = 0; i < _tables.size(); i++){if (_tables[i]){return const_iterator(_tables[i], this, i);}}return end();
}// this-> const HashTable<K, T, KeyOfT, Hash>*
const_iterator end() const	
{return const_iterator(nullptr, this, -1);
}

⭐迭代器的构造

因为我们引入了两个新的变量,所以此次构造与以往不同

代码示例(C++):

// 非const版本
__HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi)
{}// const版本
__HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht, size_t hashi):_node(node), _pht(pht), _hashi(hashi)
{}

📚3. Unordered_Set的模拟实现

🧩Unordered_Set的基本设计

代码示例(C++):

template<class K, class Hash = HashFunc<K>>
class unordered_set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:// 因为 unordered_set的特性K是不能够修改的,// 所以我们在 const迭代器和非const迭代器上,都用 const来修饰K来起到不能修改K的特点typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;typedef typename hash_bucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;pair<const_iterator, bool> insert(const K& key){auto ret = _ht.Insert(key);return pair<const_iterator, bool>(const_iterator(ret.first._node, ret.first._pht, ret.first._hashi),ret.second);}// 因为用到的都是const迭代器,所以非const迭代器我们可以不写/*iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}*/const_iterator begin() const{return _ht.begin();}const_iterator end() const{return _ht.end();}iterator find(const K& key){return _ht.Find(key);}bool erase(const K& key){return _ht.Erase(key);}private:hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
};

🌸Unordered_Set测试

代码示例(C++):

void TestSet()
{unordered_set<int> us;us.insert(1);us.insert(2);us.insert(6);us.insert(55);us.insert(3);unordered_set<int>::iterator it = us.begin();while (it != us.end()){// *it += 5;cout << *it << " ";++it;}cout << endl;
}

在这里插入图片描述


📝4. Unordered_Map的模拟实现

🧩Unordered_Map的基本设计

代码示例(C++):

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:// 在 unordered_map我们就只需要考虑 kv.first不能修改// 但是 kv.first->second是可以修改的,因此我们需要将 K用 const修饰typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _ht.Insert(kv);}iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}// 重载operator[]V& operator[] (const K& key){// 当_ht中没有就实现插入pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));return ret.first->second;}const V& operator[] (const K& key) const{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;
};

🌸Unordered_Map测试

代码示例(C++):

void TestMap()
{unordered_map<string, string> dict;dict.insert(make_pair("sort", "排序"));dict.insert(make_pair("left", "左边"));dict.insert(make_pair("right", "右边"));for (auto& kv : dict){// kv.first += 'x';kv.second += 'x';cout << kv.first << ":" << kv.second << endl;}cout << endl;
}

在这里插入图片描述


📖5. 总结

在本文的探索之旅中,我们深入剖析了unordered_map与unordered_set的内部机制,并通过模拟实现这两个容器,不仅加深了对哈希表这一重要数据结构的理解,还锻炼了编程能力和问题解决能力

通过模拟实现,我们亲手构建了哈希表,从简单的数组加链表结构,到动态扩容、再哈希等高级特性的实现,每一步都充满了挑战与收获。这个过程中,我们深刻体会到了数据结构设计的精妙之处,也学会了如何在实践中不断优化和调整我们的设计

unordered_mapunordered_set等无序容器将在更多领域发挥重要作用。随着技术的不断发展,我们也期待看到更多高效、稳定、易用的容器实现出现。

同时,我们也希望读者能够保持对新技术、新知识的好奇心和求知欲,不断探索、不断学习、不断进步
在这里插入图片描述
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

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

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

相关文章

Qt多语言功能实现

本文介绍Qt多语言功能实现。 应用程序多语言支持是常用功能&#xff0c;比如产品需要出口到不同语种的国家。采用Qt的多语言支持工具可以方便实现应用程序的多语言功能。本文以中英文语言切换为例&#xff0c;简要介绍Qt的多语言功能实现。 1.界面设计 界面设计需要考虑使用…

正则表达式与文本三剑客之grep

目录 前言 一、grep命令 二、基础正则表达式常见元字符 2.1、特殊字符 2.2、定位符 2.3、非打印字符 三、元字符操作实例 3.1、查找特定字符 3.2、利用中括号“[]”来查找集合字符 3.3、查找行首“^”与行尾字符“$” 3.4、查找任意一个字符“.”与重复字符“*” 3.…

BGP选路之Preferred value

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时&#xff0c;BGP协议会对这些BGP路由的属性进行比较&#xff0c;以确定去往该目标网络的最优BGP路由&#xff0c;然后将该最优BGP路由与去往同一目标网络的其他协议路由进行比较&#xff0c;从而决定是否将该最优…

OAuth2.0 or Spring Session or 单点登录流程

1.社交登录 2.微博社交登录 第三方登录 1.登录微博 2.点击网站接入 3.填写完信息&#xff0c;到这里&#xff0c;写入成功回调 和 失败回调 是重定向&#xff0c;所以可以写本地的地址 3.认证 分布式Session spring-session 域名不一样 发的 jSessionId 就不同&#xff0c…

自定义element主题

说明&#xff1a;这里使用的是vue3做的demo&#xff0c;所以使用的是element-plus&#xff0c;不同版本大同小异 一、安装element-plus npm install element-plus --save二、在main.ts中引入 import ElementPlus from "element-plus"; import "element-plus/d…

Kubernetes学习指南:保姆级实操手册03——规划部署

Kubernetes学习指南&#xff1a;保姆级实操手册03——规划部署 一、部署节点规划 Hostnameiprolesk8s-master0110.255.210.1masterk8s-master0210.255.210.2masterk8s-master0310.255.210.3masterk8s-node0110.255.210.4worker nodek8s-node0210.255.210.5worker nodek8s-nod…

【深度学习】LDA线性判别分析

date:2024/07/23 author:sion tag:Deeping Learn LDA(线性判别分析) 文章目录 LDA(线性判别分析)1.LDA是什么LDA是一种解决二分类问题的线性方法。它描述&#xff0c;对于给定样例集&#xff0c;将样例点投影到一条直线上&#xff0c;这条直线能使异样的样例相距远&#xff0c;…

Linus: vim编辑器的使用,快捷键及配置等周边知识详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 vim的安装创建新用户 adduser 用户名Linus是个多用户的操作系统是否有创建用户的权限查看当前用户身份:whoami** 怎么创建设置密码passwdsudo提权(sudo输入的是用户…

Golang | Leetcode Golang题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {n : len(citations)return n - sort.Search(n, func(x int) bool { return citations[x] > n-x }) }

数驭未来,景联文科技构建高质大模型数据库

国内应用层面的需求推动AI产业的加速发展。根据IDC数据预测&#xff0c;预计2026年中国人工智能软件及应用市场规模会达到211亿美元。 数据、算法、算力是AI发展的驱动力&#xff0c;其中数据是AI发展的基石&#xff0c;中国的数据规模增长速度预期将领跑全球。 2024年《政府工…

go语言day15 goroutine

Golang-100-Days/Day16-20(Go语言基础进阶)/day17_Go语言并发Goroutine.md at master rubyhan1314/Golang-100-Days GitHub 第2讲-调度器的由来和分析_哔哩哔哩_bilibili 一个进程最多可以创建多少个线程&#xff1f;-CSDN博客 引入协程 go语言中内置了协程goroutine&#…

Python实现图片相似度比较之SSIM

Python实现图片相似度比较之SSIM 解读 SSIM 数值 结构相似性指数 (SSIM) 是用来衡量两张图像相似度的指标&#xff0c;范围从 -1 到 1&#xff1a; 1 表示完全相同。0 表示没有任何相似性。负值 表示图像之间的差异超出了正常范围&#xff08;通常是因为两张图像的内容差异非…

积木报表-自定义报表

文章目录 一、springboot初始项目集成积木报表二、springboot正式项目集成积木报表注意点注意点1&#xff1a;依赖下载失败原因&#xff1a;Maven私服设置注意点2&#xff1a;dependency在【springboot初始项目集成积木报表】情况下不要放在根目录的pom里&#xff0c;放子模块的…

【Python系列】详解 open 函数:文件操作的基石

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

C# 委托函数 delegate

在C#中&#xff0c;委托&#xff08;Delegate&#xff09;是一种特殊的类型&#xff0c;它可以持有对方法的引用。 委托是实现事件的基础。事件本质上是多播委托&#xff0c;允许多个方法被触发 委托允许你将方法作为参数传递给其他方法&#xff0c;或者将方法作为返回值从方法…

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示

Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 目录 Unity 之 【Android Unity 共享纹理】之 Android 共享图片给 Unity 显示 一、简单介绍 二、共享纹理 1、共享纹理的原理 2、共享纹理涉及到的关键知识点 3、什么可以实现共享 不能实现共享…

微服务安全——SpringSecurity6详解

文章目录 说明SpringSecurity认证快速开始设置用户名密码基于application.yml方式基于Java Bean配置方式 设置加密方式自定义用户加载方式自定义登录页面前后端分离认证认证流程 SpringSecurity授权web授权:基于url的访问控制自定义授权失败异常处理方法授权:基于注解的访问控制…

沐风老师3DMAX一键烘焙插件使用方法

3DMAX一键烘焙插件使用教程 3DMAX一键烘焙插件&#xff1a;从3dMax2021增加了一个新功能是全新的BakingToTexture&#xff08;烘焙到纹理&#xff09;工具。s3DMAX一键烘焙插件是新BakingToToTorTexture的简化前端。它允许用户一键烘焙某些实用程序映射&#xff08;贴图&#x…

MySQL 数据表

InnoDB存储引擎文件 InnoDB存储引擎相关的文件包括重做日志文件、表空间文件。 表空间文件 InnoDB存储引擎在设计上模仿了Oracle&#xff0c;将存储的数据按表空间进行存放。默认配置下&#xff0c;会有一个初始化大小为10MB、名为ibdata1的文件&#xff0c;该文件就是默认的…

FlutterFlame游戏实践#16 | 生命游戏 - 编辑与交互

theme: cyanosis 本文为稀土掘金技术社区首发签约文章&#xff0c;30天内禁止转载&#xff0c;30天后未获授权禁止转载&#xff0c;侵权必究&#xff01; Flutter\&Flame 游戏开发系列前言: 该系列是 [张风捷特烈] 的 Flame 游戏开发教程。Flutter 作为 全平台 的 原生级 渲…