模拟实现STL中的unordered_map和unordered_set

目录

1.unordered_map和unordered_set简介

2.unordered_map和unordered_set设计图

3.迭代器的设计

4.哈希表的设计

5.my_unordered_map和my_unordered_set代码

1.unordered_map和unordered_set简介

unordered_map和unordered_set的使用非常类似于map和set,两者之间的差异在于底层的数据结构不同,unordered_map和unordered_set的底层使用的数据结构是哈希表,map和set底层使用的数据结构是红黑树。哈希表和红黑树都是查找效率非常高的数据结构,红黑树的查找效率是O(logN),哈希表的查找效率是O(1),总体来说哈希表的查找效率略胜一筹,但是红黑树是接近平衡的二叉搜索树,具有隐藏技能 —— 中序遍历,数据有序(升序),map和set的遍历采用的就是中序遍历;也就是说,遍历map和set得到的数据是有序的,而哈希表的遍历是无序的,所以,为了区分功能相同而底层数据结构的不同的关联式容器,以哈希表为底层数据结构的map和set前加上unordered,unordered其实就是无序的意思。

2.unordered_map和unordered_set设计图

unordered_map和unordered_set底层是开散列方式实现的哈希表,要想实现unordered_map和unordered_set,需要在内部封装哈希表;但是,STL中的容器都提供统一的访问方式 —— 迭代器,所以我们还需要实现unordered_map和unordered_set的迭代器。说白了,unordered_map和unordered_set就是通过组合 哈希表 和 迭代器 来实现的。而unordered_map和unordered_set实现上的区别就是内部存储的数据不同(一个存储键值对,一个存储元素本身),但是整体的设计框架是相同的。

unordered_map和unordered_set的设计图如下:

一个问题:unordered_map中存储的是键值对,unordered_set中存储的是一个个的元素,而二者的底层使用的数据结构都是 开散列实现的哈希表,那我们需要将哈希表实现两份吗?这个问题和map和set中数据存储的问题相同,如果实现两份的话,就会造成代码重复和冗余;解决方案也是和map、set中解决该问题的方式相同。请看下图:

可以看出,在使用上,unordered_set传递一个模板参数,unordered_map传递两个模板参数,但是在unordered_map和unordered_set中封装的哈希表都需要传递两个参数;所以unordered_map中将K类型传给底层哈希表的第一个参数,用 K 和 V封装出pair<K,V>类型传给 底层哈希表的第二个参数;unordered_set中传递给底层哈希表的第一个和第二个参数的类型都是K。这样,哈希表中第二个模板参数T就是哈希表中实际存储的数据类型。于是,就实现了复用同一个 哈希表的类模板。

那第一个模板参数是不是没用呢?并不是,因为,unordered_map和unordered_set的使用上是以Key值  (K类型的数据) 为主的,并且有些操作也是根据Key值来进行的,比如:查找操作。所以我们也是需要单独的K类型的数据的。

获取数据中的Key值问题

由于同一个类模板的哈希表中经常涉及数据的比较,unordered_set中数据的比较是按照Key值来比较的,unordered_map中数据的比较也是按照Key值来比较的。但是在同一个类模板的哈希表中不能使用同样的方式获取Key值,所示实现一个获取Key值的仿函数,该仿函数作为参数传递给哈希表。

实现代码如下:

// unordered_map中获取Key值的仿函数
struct MapKeyOfT
{const K& operator()(const pair<K, V>& kv){return kv.first;}
};// unordered_set中获取Key值的仿函数
struct SetKeyOfT
{const K& operator()(const K& key){return key;}
};

3.迭代器的设计

unordered_map和unordered_set迭代器的设计不同于map和set,map和set的迭代器的操作主要是是在一棵二叉搜索树上进行,所以封装结点的指针即可;但是unordered_map和unordered_set的迭代器的操作是在哈希表上进行的,而哈希表是由 _table _table下挂的一个个的结点组成的,所以 unordered_map 和 unordered_set 的迭代器需要封装 哈希表 和 结点的指针 (对于哈希表的封装,也采用指针的形式) 。

迭代器总体设计图如下:

迭代器的那些操作

operator* 和 operator->操作:迭代器模仿的是指针的操作,指针常用的操作就是 解引用 * 和 箭头访问操作符 ->;operator* 用于取出结点中的数据,operator->用于返回节点中数据的地址。代码如下:

T& operator*()
{return _node->_data;
}T* operator->()
{return &(_node->_data);
}

迭代器的++操作:迭代器的++操作用于实现 用迭代器遍历哈希表中的数据,所以我们需要依次遍历桶,如果桶不为空,就遍历桶中的数据,遍历完当前桶中的数据之后,再遍历下一个桶中的数据;如果桶为空,直接遍历下一个桶;迭代器++操作代码如下:

		Self& operator++(){if (_node->_next){// 当前桶还是节点_node = _node->_next;}else{// 当前桶走完了,找下一个桶KeyOfT kot;Hash hs;size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();// 找下一个桶hashi++;while (hashi < _ht->_tables.size()){if (_ht->_tables[hashi]){_node = _ht->_tables[hashi];break;}hashi++;}// 后面没有桶了if (hashi == _ht->_tables.size()){_node = nullptr;}}return *this;}

判断相等和不相等操作:迭代器判断相等和不相等,只需要判断迭代器中 结点的指针是否相等。代码如下所示:

        bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}

4.哈希表的设计

哈希表的实现有闭散列和开散列两种方式,我们采用开散列的方式实现,哈希表的设计图如下所示:

哈希函数

哈希表主要通过哈希函数来计算出 存储的数据 和 数据存储的位置 之间的映射关系。在该设计中,我们采用 除留余数法 来计算 存储元素 和 存储位置 之间的映射关系;但是,该方法只适用于整形的数据,因为并不是所有类型的数据都能进行取余运算,所以,对于一些不能取余的类型的数据,我们需要提供一个仿函数来计算出其哈希值,方便其进行取余运算,从而计算出数据的存储位置。

哈希函数示例代码如下:

template<class K>
struct HashFunc
{size_t operator()(const K& key){return (size_t)key;}
};
// 特化
template<>
struct HashFunc<string>
{size_t operator()(const string& s){size_t hash = 0;for (auto e : s){hash += e;hash *= 131;}return hash;}
};

哈希表中的操作

begin()和end()操作:begin()用于返回哈希表中第一个结点的迭代器,end()用于返回最后一个结点的下一个位置的迭代器,其实就是空。

代码实现如下:

    iterator begin(){for (size_t i = 0; i < _tables.size(); i++){// 找到第一个桶的第一个节点if (_tables[i]){return iterator(_tables[i], this);}}return end();}iterator end(){return iterator(nullptr, this);}

数据的插入:哈希表中插入数据是哈希表的精髓,因为数据的插入位置和数据之间通过哈希函数建立一 一映射的关系,通过数据的值,就可以很快的判断出数据存储的位置;并且通过限制负载因子来防止桶中的数据过多,从而为飞速的查找效率打下基础。

开散列的哈希表中的数据的插入采用头插的方式,代码实现如下:

bool Insert(const T& data)
{KeyOfT kot;if (Find(kot(data)))return false;Hash hs;// 负载因子到1就扩容if (_n == _tables.size()){vector<Node*> newTables(_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 = hs(kot(cur->_data)) % newTables.size();cur->_next = newTables[hashi];newTables[hashi] = cur;cur = next;}_tables[i] = nullptr;}_tables.swap(newTables);}size_t hashi = hs(kot(data)) % _tables.size();Node* newnode = new Node(data);// 头插newnode->_next = _tables[hashi];_tables[hashi] = newnode;++_n;return true;
}

数据的查找:在哈希表中查找一个值,首先通过哈希函数计算出该元素在哈希表中的第几个桶,然后遍历该桶下的数据,找到了就返回该结点的地址,没找到就返回空。

代码如下:

Node* 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 cur;}cur = cur->_next;}return nullptr;
}

数据的删除:删除一个数据的时候,首先要找到该数据所在的结点,找到该结点之后,删除即可。如果不存在该数据,则返回false。

删除代码如下:

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){prev->_next = cur->_next;}else{_tables[hashi] = cur->_next;}delete cur;--_n;return true;}prev = cur;cur = cur->_next;}return false;
}

5.my_unordered_map和my_unordered_set代码

my_unordered_map代码如下:

#include "Open_HashTable.h"namespace wall
{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;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const pair<K, V>& kv){return _ht.Insert(kv);}bool erase(const K& key){_ht.Erase(key);}iterator find(const K& key){Node* ret = Find(key);return iterator(ret);}private:hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};
}

my_unordered_set代码如下:

#include "Open_HashTable.h"namespace wall
{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;iterator begin(){return _ht.begin();}iterator end(){return _ht.end();}bool insert(const K& key){return _ht.Insert(key);}bool erase(const K& key){_ht.Erase(key);}iterator find(const K& key){Node* ret = Find(key);return iterator(ret);}private:hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;};
}

总结:可以看出,模拟实现的unordered_map和unordered_set主要是对 哈希表迭代器进行了组合和封装,通过添加一些操作来更加方便的使用底层的数据结构。

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

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

相关文章

Python “集合” 100道实战题目练习,巩固知识、检查技术

本文主要是作为Python中列表的一些题目&#xff0c;方便学习完Python的集合之后进行一些知识检验&#xff0c;感兴趣的小伙伴可以试一试&#xff0c;含选择题、判断题、实战题、填空题&#xff0c;答案在第五章。 在做题之前可以先学习或者温习一下Python的列表&#xff0c;推荐…

高级大数据开发学习路线指南

掌握大数据技术是一项系统性工程&#xff0c;涉及到广泛的技能和专业知识。为了帮助初学者构建坚实的基础&#xff0c;并逐步成长为大数据领域的专家&#xff0c;下面详细阐述了一条全面而深入的学习路线&#xff1a; 1. Java 编程基础 - 打造坚实的底层技能 关键知识点&…

训练加速和推理加速

1. 训练加速 训练加速指的是通过优化技术、硬件加速等方式&#xff0c;减少训练模型的时间&#xff0c;尤其是对于大规模数据集和复杂模型。 训练的特点&#xff1a; 计算量大&#xff1a;模型训练时需要执行前向传播和反向传播&#xff0c;并在多个迭代&#xff08;epoch&a…

microchip中使用printf给AVR单片机串口重定向

重定向中修改需要的串口 #ifndef USART1_H_ #define USART1_H_#ifndef F_CPU #define F_CPU 11059200UL #endif #define BAUDRATE 9600 #include <avr/io.h> #include <avr/interrupt.h>#include <stdio.h> #include <string.h>#define PRINT /*…

计算机专业毕设-校园新闻网站

1 项目介绍 基于SSMVue的校园新闻网站&#xff1a;后端 SpringBoot、Mybatis-Plus&#xff0c;前端VueElementUI&#xff0c;具体功能如下&#xff1a; 管理端 个人中心&#xff1a;查看和修改个人信息用户管理新闻类型管理校园新闻管理留言板管理论坛交流管理轮播图管理 用…

开放标准如何破解企业数字化与可持续发展的困境:The Open Group引领生态系统架构创新

应对数字化与可持续发展的双重挑战&#xff0c;开放标准是关键 在当今快速变化的商业环境中&#xff0c;企业不仅需要通过数字化转型提升竞争力&#xff0c;还面临日益严格的可持续发展要求。开放标准正在成为企业破解这一双重挑战的核心工具。The Open Group 2024生态系统架构…

视频监控摄像头国标GB28181配置参数逐条解析

转载&#xff1a;视频监控摄像头国标GB28181配置参数逐条解析 现在的很多信息化项目&#xff0c;都会涉及到国标GB28181的视频监控产品&#xff0c;当我们配置这些国标平台&#xff0c;录像机&#xff0c;摄像头时&#xff0c;如果对相关参数的定义不清楚的话&#xff0c;会给我…

20240919在友善之臂的NanoPC-T6开发板上使用Rockchip原厂的Buildroot时打开CONFIG_USB_NET_RNDIS_HOST宏

20240919在友善之臂的NanoPC-T6开发板上使用Rockchip原厂的Buildroot时打开CONFIG_USB_NET_RNDIS_HOST宏 2024/9/19 18:20 缘起&#xff0c;大毛PK二毛战况激烈&#xff0c;穿越机大卖&#xff01;我司拆同行的图传作品。 发现&#xff1a; 主控&#xff1a;飞凌OK3588-C核心板…

【Python常用模块】_cx_Oracle模块详解

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

演示:基于WPF的自绘的中国地铁轨道控件

一、目的&#xff1a;演示一个基于WPF的自绘的中国地铁轨道控件 二、效果演示 北京地铁 成都地铁 上海地铁 深圳地铁 南京地铁 长春地铁 哈尔滨地铁 武汉地铁 厦门地铁 香港地铁 三、功能 支持平移、缩放等操作 鼠标悬停显示线路信息和站点信息 按表格显示&#xff0c;按纸张…

使用 nvm 管理 node 版本:如何在 macOS 和 Windows 上安装使用nvm

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、nvm的安装与基本使用2.1 macOS安装nvm2.1.1 使用 curl 安装2.1.2 使用 Homebrew 安装 2.2 Windows安装nvm2.2.1 下载 nvm-windows2.2.2 安装 nvm-windows 2.3 安装node2.4 切换node版本 三、常见问题及解决方案…

2024年数学建模比赛题目及解题代码

目录 一、引言 1. 1竞赛背景介绍 1.1.1数学建模竞赛概述 1.1.2生产过程决策问题在竞赛中的重要性 1.2 解题前准备 1.2.2 工具与资源准备 1.2.3 心态调整与策略规划 二、问题理解与分析 三、模型构建与求解 3.1 模型选择与设计 3.1.1 根据问题特性选择合适的数学模型类…

鸿蒙OpenHarmony【轻量系统芯片移植案例】标准系统方案之扬帆移植案例

标准系统方案之扬帆移植案例 ​ 本文章是基于瑞芯微RK3399芯片的yangfan开发板&#xff0c;进行标准系统相关功能的移植&#xff0c;主要包括产品配置添加&#xff0c;内核启动、升级&#xff0c;音频ADM化&#xff0c;Camera&#xff0c;TP&#xff0c;LCD&#xff0c;WIFI&a…

观成科技:新版suo5隧道工具加密流量跟踪分析

1、工具简介 suo5是一个高性能的http隧道代理工具&#xff0c;支持全双工半双工模式&#xff0c;具有很高的传输性能&#xff0c;现在越来越多的webshell管理工具与内存马生成工具都支持了生成suo5木马的功能。从v1.1.0版本开始&#xff0c;其TLS协议传输功能中&#xff0c;增…

青海省基础地理矢量数据

青海是长江、黄河、沧澜江的发源地&#xff0c;因此被誉为“三江源”和“中华水塔”&#xff0c;具有全球独一无二的生态地位。青海省拥有丰富的水资源和生物多样性&#xff0c;对维护亚洲的水资源和生态平衡具有重要作用。 该数据集涵盖了青海省内的主要地理要素&#xff0c;包…

聚焦汽车智能化与电动化,亚洲领先的汽车工业技术博览会 2025年11月与您相约 AUTO TECH 华南展

抢占市场先机︱聚焦汽车智能化与电动化&#xff0c;亚洲领先的汽车工业技术博览会 2025年11月与您相约 AUTO TECH 华南展 随着汽车智能化与电动化的迅猛发展&#xff0c;汽车电子技术、车用功率半导体技术、智能座舱技术、轻量化技术/材料、软件定义汽车、EV/HV技术、测试测量技…

deepspeed安装报错 No module named ‘dskernels‘解决

pip install deepseek安装报错 Using cached https://pypi.tuna.tsinghua.edu.cn/packages/61/e6/04e2f2de08253e6b779fe7706f2e06d8fb48353e1d33a2fd7805062213d4/deepspeed-0.12.3.tar.gz (1.2 MB)Preparing metadata (setup.py) ... errorerror: subprocess-exited-with-err…

透明屏幕有普通屏幕有哪些优点

针对透明玻璃屏幕的安装方案&#xff0c;我们需要综合考虑多个因素&#xff0c;包括安装环境、屏幕尺寸、重量、安全要求以及视觉效果等。以下是一个概括性的安装方案框架&#xff0c;供您参考&#xff1a; 一、前期准备 1.1 需求分析 明确透明玻璃屏幕的使用场景&#xff08…

C++速通LeetCode中等第4题-三数之和

解题思路&#xff1a;先排序&#xff0c;固定第一个数&#xff0c;用两个指针分别指向右侧剩余数列的两端&#xff0c;右侧向左移动直到两指针重合&#xff0c;看三数合有没有解&#xff0c;指针遇到相同数字跳过。 class Solution { public:vector<vector<int>> …

【数据库】MySQL内置函数

本篇分享一些在MySQL中常见的一些内置函数&#xff0c;如日期函数&#xff0c;字符串函数和数学函数&#xff0c;以方便于操作数据库中的数据。 1.日期函数 我们先整体观察一下这些函数再讲解案例 日期函数使用起来都非常就简单 获得年月日&#xff1a; select current_dat…