【C++】模拟实现unordered_map和unordered_set

news2024/7/4 6:04:17

哈希表封装

  • 前言
  • 正式开始
    • 模型修改
    • Insert修改
    • 迭代器
      • 运算符重载
    • Find
    • operator[ ]

在这里插入图片描述

前言

本篇以前一篇模拟实现哈希表为基础进行改造,如果没看过前一篇的先看一下:【C++】模拟实现哈希(闭散列和开散列两种方式)。

由于本篇代码基于上篇中开散列的代码进行改造,我就先把上篇中开散列实现的哈希表代码放到这里,各位可以不用看:

#pragma once

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<>
struct HashFunc<string>
{
	size_t operator()(const string& str)
	{
		size_t res = 0;
		for (auto c : str)
		{
			res += c;
			res *= 131;
		}
		return res;
	}
};


namespace FangZhang_OpenHash
{
	template<class K, class V>
	struct HashNode
	{
		HashNode(const pair<K, V>& kv = make_pair(K(), V()))
			:_kv(kv)
			, _next(nullptr)
		{}

		pair<K, V> _kv;
		HashNode<K, V>* _next;
	};

	template<class K, class V, class Hash = HashFunc<K>>
	class HashTable
	{
		typedef HashNode<K, V> Node;

	private:
		vector<Node*> _tables;
		size_t _size = 0;

	public:

		~HashTable()
		{
			for (int i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						delete cur;
						cur = next;
					}

					_tables[i] = nullptr;
				}
			}
			
			_size = 0;
		}

		bool Insert(const pair<K, V>& kv)
		{
			// 去重
			if (Find(kv.first))
			{
				return false;
			}
			
			// 扩容
			// 此处扩容就不需要让负载因子为0.7了
			// 直接_size == _tables.size()就行
			// 因为不会出现占用其他数据位置的情况
			if (_size == _tables.size())
			{
				size_t newSize = __stl_next_prime(_size);
				// 这里可以利用前面开辟好的节点
				// 所以就不需要像闭散列那样再整一个哈希表了
				// 复用Insert的话开销比较大,还要重新给节点开空间
				// 所以直接给一个vector利用前面开的节点就好
				vector<Node*> newTables;
				newTables.resize(newSize);
				for (auto& node : _tables)
				{
					Node* cur = node;
					Hash hash;
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hash(cur->_kv.first) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}

					node = nullptr;
				}

				_tables.swap(newTables);
			}

			// 真正插入
			Hash hash;
			size_t hashi = hash(kv.first) % _tables.size();

			// 头插
			Node* newNode = new Node(kv);
			newNode->_next = _tables[hashi];
			_tables[hashi] = newNode;

			++_size;

			return true;
		}

		Node* Find(const K& key)
		{
			if (_tables.size() == 0)
				return nullptr;

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
					return cur;

				cur = cur->_next;
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			if (!Find(key))
			{
				return false;
			}

			Hash hash;
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur && cur->_kv.first != key)
			{
				prev = cur;
				cur = cur->_next;
			}

			if (prev)
			{
				prev->_next = cur->_next;
			}
			else
			{
				_tables[hashi] = cur->_next;
			}
				
			delete cur;

			return true;
		}

		// 表的长度
		size_t TablesSize()
		{
			return _tables.size();
		}

		// 桶的个数
		size_t BucketNum()
		{
			size_t num = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					++num;
				}
			}

			return num;
		}

		// 最长桶的长度
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _tables[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}

				//if (len > 0)
					//printf("[%d]号桶长度:%d\n", i, len);

				if (len > maxLen)
				{
					maxLen = len;
				}
			}

			return maxLen;
		}

		size_t Size()
		{
			return _size;
		}

	private:
		inline size_t __stl_next_prime(size_t n)
		{
			static const size_t __stl_num_primes = 28;
			static const size_t __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
			};

			for (size_t i = 0; i < __stl_num_primes; ++i)
			{
				if (__stl_prime_list[i] > n)
				{
					return __stl_prime_list[i];
				}
			}

			return -1;
		}
	};
	
}

正式开始

首先要先搞两个头文件 UnOrderedMap.h 和 UnOrderedSet.h,这样的起名是为了不和库中的冲突。并将map和set写在命名空间FangZhang中。

模型修改

上面模拟实现中的哈希表是K/V模型的,我们这里要改成既能实现K/V模型的,又能实现K模型的。和我前面模拟实现map和set很相似。要将原来哈希表代码中的模版参数由KV改为T,并将所有的pair类型都换为模版T。
在这里插入图片描述

在这里插入图片描述

并在map和set中封装:
在这里插入图片描述

在这里插入图片描述

因为上面map和set中增加了第三个模版参数Hash并将其传给了哈希表,所以在哈希表中就不需要让第三个模版参数Hash给默认值HashFunc<K>了。

Insert修改

Insert要改一下参数,改为data:
在这里插入图片描述
data表示当前哈希表中存放的数据,但是有一个问题,就是data的类型T我们不能确定,当模型是K时,有可能是int,但当模型是KV时,也有可能是pair<int, int>。

当代码改为如下时(粗略过一眼就行):
在这里插入图片描述

其中hash函数中传参应该传的是key,而非data,当data为pair类型是key就是其first,当data为key时就直接是data就行,所以我们可以写一个仿函数,将仿函数写在map和set中,传过去data,返回来key就好。所以哈希表中又要加一个模版参数KeyOfT,用来提取出每个data的key。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
把Insert再改一下:
在这里插入图片描述

在map和set中封装一下insert:
在这里插入图片描述
在这里插入图片描述

测试一下:
在这里插入图片描述

在这里插入图片描述

但上面没写迭代器,不能打印,我们下面实现一下迭代器。

迭代器

实现前要想清楚逻辑,当我们想遍历哈希表时,要挨着节点遍历,但中间有不想连的地方,如果其中一个非空链表走到头了,想要找下一个非空链表就得要借助其本身的哈希表,所以我们要在迭代器中给一个哈希表的指针,用来方便我们遍历。

在这里插入图片描述

运算符重载

我们这里重载一下*、->、!=、++运算符,就能遍历了。

先说前三个:
在这里插入图片描述

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

再在哈希表中写一下begin和end:

在这里插入图片描述
并在map中封装一下:

在这里插入图片描述

测试,发现出问题了,在哈希表中_ptb指针所指向的是整个哈希表结构体,其中解引用了_tables,而_tables在哈希表中是私有的,不可直接在迭代器中访问。
在这里插入图片描述

而哈希表中也用到了迭代器,所以二者会出现相互调用的问题,此时我们就要用友元来解决这个问题了。迭代器中无法访问哈希表中的成员,所以我们要将迭代器设置为哈希表的友元。
在这里插入图片描述

再测试:
在这里插入图片描述

迭代器有了范围for也就能跑了:
在这里插入图片描述

Find

改一下Find,返回值改为迭代器:
在这里插入图片描述

重载一下迭代器的==,再改一下Erase前面的Find:
在这里插入图片描述

我们用一下统计数量的测试:
在这里插入图片描述
但是没有重载[ ]就比较麻烦,所以再来重载一下[ ]。只能在unordered_map中重载。

operator[ ]

如果看过我红黑树的模拟实现的话,这里就比较熟悉了。
我们直接复用一下insert就好。

但是要把insert的返回值改一下:
在这里插入图片描述

再把unordered_map中的insert也改一下:
在这里插入图片描述

再来重载[ ]:
在这里插入图片描述

前面的博客中说过了,这里再解释一下:res.first就是对应插入节点的iterator,->返回_data的地址,但是编译器优化了,省略了一个->,所以就能直接得到_data,然后直接拿到second,也就是KV中的V。

测试:
在这里插入图片描述

unordered_set就不搞了,比unordered_map还简单,感兴趣的老铁自己搞搞。这里模拟实现不是为了把所有的实现,主要是了解一下STL库的底层,更方便于我们学习。以后是绝对用不到这写模拟的。

到此结束。。。

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

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

相关文章

编译工具:CMake(五) | 静态库与动态库构建

编译工具&#xff1a;CMake&#xff08;五&#xff09; | 静态库与动态库构建 建立共享库编译共享库指令 ADD_LIBRARY 添加静态库动态库版本号安装共享库和头文件 建立共享库 在Compilation_tool/cmake路径下建立test3目录 在 t3 目录下建立 CMakeLists.txt&#xff0c;内容如…

88-基于stm32单片机空调温湿度控制系统Proteus仿真+源码

资料编号&#xff1a;088 一&#xff1a;功能介绍&#xff1a; 1、采用stm32单片机OLED显示屏DHT11温湿度电机按键&#xff0c;制作一个温湿度采集&#xff0c;OLED显示相关数据&#xff0c;自动制冷制热空调&#xff1b; 2、通过按键设置温度值和工作模式&#xff08;制冷/制热…

MLC LLM:将LLMs部署到消费类硬件的优势、挑战以及解决方案

一、前言 随着生成人工智能&#xff08;AI&#xff09;和大语言模型&#xff08;LLM&#xff09;的快速发展&#xff0c;基于LLM的应用越来越普及。然而&#xff0c;大规模的模型需要密集计算和庞大的资源&#xff0c;使得许多公司无法承担从头开始训练模型的成本。目前&#…

计算机控制技术|17/8|16:27

目录 1. 什么是“纹波”现象&#xff1f;原因为何&#xff1f; 2. 如何解决最小拍控制器的“纹波”问题&#xff1f; 3. 解释“阻尼因子法”&#xff1f; 4. 大林算法控制器设计时&#xff0c;给定闭环系统传递函数模型离散化时为何要加入零阶保持器&#xff1f; 5. 何为“…

86-基于stm32单片机智能家居温湿度监测自动加湿器散热器Proteus仿真+源码

资料编号&#xff1a;086 一&#xff1a;功能介绍&#xff1a; 1、采用stm32单片机OLED显示屏DHT11温湿度电机按键蜂鸣器&#xff0c;制作一个温湿度采集、OLED显示相关数据&#xff0c; 2、通过按键设置温度上限、湿度下限、 3、当采集温度大于设置温度&#xff0c;开启散热电…

【广州华锐视点】VR线上教学资源平台提供定制化虚拟现实学习内容

虚拟现实&#xff08;VR&#xff09;技术的出现为我们提供了一种全新的在线教学方式。由广州华锐视点开发的VR线上教学资源平台&#xff0c;作为一个综合性的学习工具&#xff0c;正在教育领域迅速发展&#xff0c;并被越来越多的教育机构和学生所接受。那么&#xff0c;VR线上…

XDR解决方案成为了新的安全趋势

和当今指数倍增长的安全数据相比&#xff0c;安全人才的短缺带来了潜在的风险。几乎所有的公司&#xff0c;无论规模大小&#xff0c;在安全资源能力上都有限&#xff0c;需要过滤各种告警才能将分析量保持在可接受范围。但这样一来&#xff0c;潜在的威胁线索就可能被埋没&…

在一小时内构建您的深度学习应用程序

一、说明 我已经做了将近十年的数据分析。有时&#xff0c;我使用机器学习技术从数据中获取见解&#xff0c;并且我习惯于使用经典 ML。 虽然我已经通过了神经网络和深度学习的一些MOOC&#xff0c;但我从未在我的工作中使用过它们&#xff0c;这个领域对我来说似乎很有挑战性。…

爬虫的代理IP池写哪里了?

亲爱的程序员小伙伴们&#xff0c;想要提高爬虫效率和稳定性&#xff0c;组建一个强大的代理IP池是非常重要的一步&#xff01;今天我就来和你分享一下&#xff0c;代理IP池到底应该写在哪里&#xff0c;以及如何打造一个令人瞩目的代理IP池&#xff01;准备好了吗&#xff1f;…

【最新可用】VMware中ubuntu与主机window之间使用共享文件夹传输大文件

一、VMware设置共享文件夹 &#xff08;1&#xff09;虚拟机关机情况下&#xff0c;创建一个共享文件夹 &#xff08;2&#xff09;ubuntu中挂载共享文件夹 1、如果之前已经挂载 hgfs&#xff0c;先取消挂载 sudo umount /mnt/hgfs2、重新使用以下命令挂载 sudo /usr/bin/vmh…

11、BigKey

BigKey 阿里广告平台&#xff0c;海量数据里查询某一固定前缀的key 小红书&#xff0c;你如何生产上限制keys */flushdb/flushall等危险命令以防止误删误用&#xff1f; 美团&#xff0c;MEMORY USAGE 命令你用过吗&#xff1f; BigKey问题&#xff0c;多大算big&#xff1f;你…

数据库名字添加中文

Jetbrains 可以呀&#xff0c;这个ui 相当棒 from database import Sqlite3Database from googletrans import Translator import csvif __name__ "__main__":TRANS_EN2ZH Falsetranslator Translator()sqlite Sqlite3Database("./drurmu.db")sqlite.r…

深入篇【C++】手搓模拟实现二叉搜索树(递归/非递归版本)常见应用场景(K模型与KV模型)

深入篇【C】手搓模拟实现二叉搜索树(递归/非递归版本&#xff09;&&常见应用场景 Ⅰ.二叉搜索树概念Ⅱ.二叉搜索树模拟实现(递归与非递归)①.定义结点②.构造二叉树③.插入结点④.删除结点(重要)⑤.查找结点⑥.析构二叉树⑦.拷贝二叉树⑧.二叉树赋值 Ⅲ.二叉搜索树应用…

Cadence+硬件每日学习十个知识点(38)23.8.18 (Cadence的使用,界面介绍)

文章目录 1.Cadence有共享数据库的途径2.Cadence启动3.Cadence界面菜单简介&#xff08;file、edit、view、place、options&#xff09;4.Cadence界面的图标简介5.我的下载资源有三本书 1.Cadence有共享数据库的途径 答&#xff1a; AD缺少共享数据库的途径&#xff0c;目前我…

Apache-DBUtils

目录 封装方法 引出dbutils 案例 当关闭connection后&#xff0c;resultset结果集就无法使用了&#xff0c;这就使得resultset不利于数据的管理 封装方法 我们可以将结果集先存储在一个集合中&#xff0c;当connection关闭后&#xff0c;我们可以通过访问集合来访问结果集 …

Educational Codeforces Round 110 (Rated for Div. 2) C. Unstable String

dp写法&#xff1a;f[i][j]表示第i位&#xff0c;当前位为j&#xff0c;能往前找的最大的合法长度。 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #define endl \nusing namespace std;typedef pair<int, int> PII; type…

Redis进阶篇 - 04发布订阅、布隆过滤器、过期策略、回收机制、管道...核心知识原理

Redis底层原理篇&#xff0c;​让学习绚丽多彩起来&#xff01;&#xff01;&#xff01;&#xff08;需要原图私信&#xff09;

【服务器】Strace显示后台进程输出

今天有小朋友遇到一个问题 她想把2331509和2854637这两个进程调到前台来&#xff0c;以便于在当前shell查看这两个python进程的实时输出 我第一反应是用jobs -l然后fg &#xff08;参考这里&#xff09; 但是发现jobs -l根本没有输出&#xff1a; 原因是jobs看的是当前ses…

【Docker】Docker Desktop配置资源:cpu、内存等(windows环境下)

Docker Desktop配置资源&#xff1a;cpu、内存等&#xff08;windows环境下&#xff09; 一、WSL2 以及 hyper-v区别&#xff0c;二者安装docker desktop1.WSL2和hyper-v区别2.安装Docker Desktop 二、docker desktop限额配置&#xff0c;资源配置方法 Docker 是指容器化技术&a…

Redis进阶底层原理- Redis结构图与底层数据编码结构

Redis底层原理篇&#xff0c;​让学习绚丽多彩起来&#xff01;&#xff01;&#xff01;&#xff08;需要原图私信&#xff09;