【C++、数据结构】封装unordered_map和unordered_set(用哈希桶实现)

news2025/2/27 1:54:21

文章目录

  • 📖 前言
  • 1. 复用同一个哈希桶⚡
    • 1.1 🌀修改后结点的定义
    • 1.2 🌀两个容器各自模板参数类型:
  • 2. 改造之后的哈希桶⛳
  • 3. 哈希桶的迭代器🔥
    • 3.1 💥哈希桶的begin()和 end()的定义
    • 3.2 💥 operator* 和 operator->
    • 3.3 💥 operator++
    • 3.4 💥 operator== 和 operator!=
  • 4. 封装unordered_map和unordered_set⭕

📖 前言

与学习红黑树和map、set的思路一样,我们在学unordered_map和unordered_set时,也是先学底层结构,在用模拟的底层结构来自己封装一下该容器,动手实践来让我们更好的学习和理解底层逻辑。

前情回顾:哈希桶 👉 传送门

这里用到的封装思路和封装map、set的思路相同,都是更高维度的泛型编程。

思路复习:封装map和set 👉 传送门


1. 复用同一个哈希桶⚡

如何复用同一个哈希桶,我们就需要对哈希桶进行改造,将哈希桶改造的更加泛型一点,既符合Key模型,也符合Key_Value模型。

1.1 🌀修改后结点的定义

在这里插入图片描述
所以我们这里还是和封装map和set时一样,无论是Key还是Key_Value,都用一个类型T来接收,这里高维度的泛型哈希表中,实现还是用的是Kye_Value模型,K是不能省略的,同样的查找和删除要用。

1.2 🌀两个容器各自模板参数类型:

在这里插入图片描述
如何取到想要的数据:

  • 我们给每个容器配一个仿函数
  • 各传不同的仿函数,拿到想要的不同的数据

同时我们再给每个容器配一个哈希函数。

2. 改造之后的哈希桶⛳

//K --> 键值Key,T --> 数据
//unordered_map ->HashTable<K, pair<K, V>, MapKeyOfT> _ht;
//unordered_set ->HashTable<K, K, SetKeyOfT> _ht;
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
	template<class K, class T, class KeyOfT, class HashFunc>
	friend class __HTIterator;

	typedef HashNode<T> Node;
public:
	typedef __HTIterator<K, T, KeyOfT, HashFunc> iterator;

	iterator begin()
	{
		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);
	}

	~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;
		}
	}

	size_t GetNextPrime(size_t prime)
	{
		const int PRIMECOUNT = 28;
		static const size_t primeList[PRIMECOUNT] =
		{
			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
		};

		//获取比prime大那一个素数
		size_t i = 0;
		for (i = 0; i < PRIMECOUNT; i++)
		{
			if (primeList[i] > prime)
				return primeList[i];
		}

		return primeList[i];
	}

	pair<iterator, bool> Insert(const T& data)
	{
		HashFunc hf;
		KeyOfT kot;

		iterator pos = Find(kot(data));
		if (pos != end())
		{
			return make_pair(pos, false);
		}

		//负载因子 == 1 扩容 -- 平均每个桶挂一个结点
		if (_tables.size() == _n)
		{
			//size_t newSize = _tables.size() == 0 ? 10 : _tables.size() * 2;
			size_t newSize = GetNextPrime(_tables.size());

			if (newSize != _tables.size())
			{
				vector<Node*> newTable;
				newTable.resize(newSize, 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)) % newSize;

						//转移到新的表中
						cur->_next = newTable[hashi];
						newTable[hashi] = cur;
						cur = next;
					}

					//将原表置空
					_tables[i] = nullptr;
				}
				newTable.swap(_tables);
			}
			
		}

		size_t hashi = hf(kot(data));
		hashi %= _tables.size();

		//头插到对应的桶即可
		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)
	{
		if (_tables.size() == 0)
		{
			return iterator(nullptr, this);
		}

		KeyOfT kot;
		HashFunc hf;
		size_t hashi = hf(key);
		//size_t hashi = HashFunc()(key);

		hashi %= _tables.size();
		Node* cur = _tables[hashi];

		//找到指定的桶之后,顺着单链表挨个找
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				return iterator(cur, this);
			}

			cur = cur->_next;
		}

		//没找到返回空
		return iterator(nullptr, this);
	}

	bool Erase(const K& key)
	{
		if (_tables.size() == 0)
		{
			return false;
		}

		HashFunc hf;
		KeyOfT kot;
		size_t hashi = hf(key);
		hashi %= _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;

				return true;
			}

			prev = cur;
			cur = cur->_next;
		}

		return false;
	}
private:
	//指针数组
	vector<Node*> _tables;
	size_t _n = 0;
};

研究表明:除留余数法,最好模一个素数

  • 通过查STL官方库我们也发现,其提供了一个取素数的函数
  • 所以我们也提供了一个,直接拷贝过来
    • 这样我们在扩容时就可以每次给素数个桶
    • 在扩容时加了一条判断语句是为了防止素数值太大,过分扩容容易直接把空间(堆)干崩了

3. 哈希桶的迭代器🔥

3.1 💥哈希桶的begin()和 end()的定义

在这里插入图片描述

  • 以第一个桶中第一个不为空的结点为整个哈希桶的开始结点
  • 以空结点为哈希桶的结束结点

3.2 💥 operator* 和 operator->

在这里插入图片描述
同之前operator->的连续优化一样,不再赘述……

3.3 💥 operator++

在这里插入图片描述
备注:

  • 这里要在哈希桶的类外面访问其私有成员
  • 我们要搞一个友元类
  • 迭代器类是哈希桶类的朋友
  • 这样就可以访问了

在这里插入图片描述

思路:

  • 判断一个桶中的数据是否遍历完
    • 如果所在的桶没有遍历完,在该桶中返回下一个结点指针
    • 如果所在的桶遍历完了,进入下一个桶
  • 判断下一个桶是否为空
    • 非空返回桶中第一个节点
    • 空的话就遍历一个桶

后置++和之前一眼老套路,不赘述

注意:

  • unordered_map和unordered_set是不支持反向迭代器的,从底层结构我们也能很好的理解(单链表找不了前驱)
  • 所以不支持实现迭代器的operator- -

3.4 💥 operator== 和 operator!=

在这里插入图片描述

template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;

//哈希桶的迭代器
template<class K, class T, class KeyOfT, class HashFunc>
class __HTIterator
{
	typedef HashNode<T> Node;
	typedef __HTIterator<K, T, KeyOfT, HashFunc> Self;
public:
	Node* _node;
	
	__HTIterator() {};

	//编译器的原则是向上查找(定义必须在前面,否则必须先声明)
	HashTable<K, T, KeyOfT, HashFunc>* _pht;

	__HTIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		, _pht(pht)
	{}

	Self& operator++()
	{
		if (_node->_next)
		{ 
			_node = _node->_next;
		}
		else//当前桶已经走完了,要走下一个桶
		{
			KeyOfT kot;
			HashFunc hf;
			size_t hashi = hf(kot(_node->_data)) % _pht->_tables.size();
			hashi++;

			//找下一个不为空的桶 -- 访问到了哈希表中私有的成员(友元)
			for (; hashi < _pht->_tables.size(); hashi++)
			{
				if (_pht->_tables[hashi])
				{
					_node = _pht->_tables[hashi];
					break;
				}
			}

			//没有找到不为空的桶,用nullptr去做end标识
			if (hashi == _pht->_tables.size())
			{
				_node = nullptr;
			}
		}

		return *this;
	}

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

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

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

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

在这里插入图片描述
编译器的原则是向上查找(定义必须在前面,否则必须先声明)


4. 封装unordered_map和unordered_set⭕

有了上面的哈希桶的改装,我们这里的对map和set的封装就显得很得心应手了。

unordered_map的封装:

template<class K, class V, class HashFunc = DefaultHash<K>>
class unordered_map
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	typedef typename Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc>::iterator iterator;

	iterator begin()
	{
		return _ht.begin();
	}

	iterator end()
	{
		return _ht.end();
	}

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

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

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

	V& operator[](const K& key)
	{
		pair<iterator, bool> ret = insert(make_pair(key, V()));
		return ret.first->second;
	}

private:
	Bucket::HashTable<K, pair<K, V>, MapKeyOfT, HashFunc> _ht;
};

这里unordered_map中的operator[ ]我们知道其原理之后,模拟实现就非常方便,直接调用插入函数,控制好参数和返回值即可。

对unordered_set的封装:

template<class K, class HashFunc = DefaultHash<K>>
class unordered_set
{
	//SteKeyOfT是set专用的就用内部类
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};

public:
	typedef typename Bucket::HashTable<K, K, SetKeyOfT, HashFunc>::iterator iterator;

	iterator begin()
	{
		return _ht.begin();
	}

	iterator end()
	{
		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:
	Bucket::HashTable<K, K, SetKeyOfT, HashFunc> _ht;
};

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

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

相关文章

Python自动化测试实战篇(5)优化selenium+unittest+ddt,搞定100条测试用例只执行前50条

这些是之前的文章&#xff0c;里面有一些基础的知识点在前面由于前面已经有写过&#xff0c;所以这一篇就不再详细对之前的内容进行描述 Python自动化测试实战篇&#xff08;1&#xff09;读取xlsx中账户密码&#xff0c;unittest框架实现通过requests接口post登录网站请求&…

【PyQt】树形控件QTreeWidget的复选框实现自动部分选择/半选择状态

为实现如下效果&#xff0c;搜索未得&#xff0c;自己总结。1 效果2 代码以下非完整代码&#xff0c;仅作演示用。2.1 引入包from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMainWindow, QApplication, QTreeWidgetItem from PyQt5.uic import loadUi import sys2.…

基于zookeeper的Hadoop集群搭建详细步骤

目录 一、一些基本概念 二、集群配置图 三、Hadoop高可用集群配置步骤 1.在第一台虚拟机解压hadoop-3.1.3.tar.gz到/opt/soft/目录 2.修改文件名、属主和属组 3.配置windows四台虚拟机的ip映射 4.修改hadoop配置文件 (1)hadoop-env.sh (2)workers (3)crore-site.xml …

微信小程序+gatewayworker+php+tp框架开发,websocke即时通讯

为了做小程序的即时通讯功能&#xff0c;查了一些资料和视频&#xff0c;记录一下。 gatewayworker在tp框架的安装 下载地址&#xff1a;https://www.workerman.net/doc/gateway-worker/ 由于我先是在本地电脑上做开发的&#xff0c;所以下载的windows的demo 解压之后&#xf…

IPv6的基础配置以及实战案例

IPv6基本配置配置IPv6静态路由[Huawei] ipv6 route-static dest-ipv6-address prefix-length { interface-type interface-number [ nexthop-ipv6-address ] | nexthop-ipv6-address } [ preference preference ]查看接口的IPv6信息[Huawei] display ipv6 interface [ interfac…

Keras实例教程(7)之构建模型的第三种方式

多年以前,在TensorFlow中搭建深度学习模型对于很多人来说其实仍然是比较困难的。相比之下,Keras作为独立于TensorFlow的一种深度学习框架则要简单很多。在TensorFlow与PyTorch的竞争中逐渐式微的情况下,TensorFlow团队终于宣布Keras将成为在tensorflow2.0中构建和训练模型的…

浅谈软件测试需求管理

什么是需求管理&#xff1f; 需求管理&#xff0c;指对产品、系统或工程的开发需求的搜集、定义、分析、评审、整理、维护、追溯和复用等相关的管理工作和流程。通常特指应用程序或软件系统的研发需求。需求管理和配置管理、测试管理、缺陷管理、风险管理、变更管理等管理流程…

Java真的不难(五十四)RabbitMQ的入门及使用

RabbitMQ的入门及使用 一、什么是RabbitMQ&#xff1f; MQ全称为Message Queue&#xff0c;即消息队列。消息队列是在消息的传输过程中保存消息的容器。它是典型的&#xff1a;生产者、消费者模型。生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。…

redis+token实现登录校验,前后端分离,及解跨域问题的4种方法

目录 一、使用自定义filter实现跨域 1、客户端向服务端发送请求 2、服务端做登录验证了&#xff0c;并生成登路用户对应的token&#xff0c;保存到redis 3、响应&#xff08;报错&#xff09;-----跨域问题 4、解决跨域问题--------服务器端添加过滤器&#xff0c;设置请求…

Mybatis流式游标查询-大数据DB查询OOM查询问题

问题场景Mysql数据处理类型分以下三种com.mysql.cj.protocol.a.result.ResultsetRowsStatic&#xff1a;普通查询&#xff0c;将结果集一次性全部拉取到内存com.mysql.cj.protocol.a.result.ResultsetRowsCursor&#xff1a;游标查询&#xff0c;将结果集分批拉取到内存&#x…

Pytorch入门教程

Pytorch入门教程 Pytorch简介 概念&#xff1a;由Facebook人工智能研究小组开发的一种基于Lua编写的Torch库的Python实现的深度学习库。优势&#xff1a;简洁、上手快、具有良好的文档和社区支持、项目开源、支持代码调试、丰富的扩展库 Pytorch基础知识 1.张量Tensor 分类…

【Ubuntu新手入门2】深度学习环境配置 Anaconda+Pycharm+PyTorch

【Ubuntu新手入门2】深度学习环境配置 AnacondaPycharmpytorch前言安装PyTorch查看cuda版本mobaxterm软件远程连接linux服务器安装安装anaconda安装pycharm安装新环境pytorch前言 本系统&#xff1a;Ubuntu18.04&#xff0c;anaconda最新&#xff0c;Pycharm最新&#xff0c;P…

泛微采知连,为组织提供安全、合规、智能的数字化文控系统

作为市场主体&#xff0c;企业需要建立健全的质量管理体系&#xff0c;并且及时更新&#xff0c;以应对激烈的市场竞争&#xff0c;实现企业可持续发展。 质量体系在很大程度上通过文件化的形式表现出来。《质量管理体系要求》(GB/T19001—2016/ISO9001&#xff1a;2015)标准指…

ESP-IDF:TCP多线程并发服务器

核心代码&#xff1a; 核心思想就是主线程只处理socket监听功能&#xff0c;把数据处理部分分配到不同的线程中去处理。来了一个客户端连接&#xff0c;就分配新的线程去处理该客户端的数据请求。 代码&#xff1a; /多线程并发服务器/ #include <stdio.h> #include …

实用调试技巧【上篇】

&#x1f534;本文章是在 Visual Studio 2022&#xff08;VS2022&#xff09;编译环境下进行操作讲解 文章目录&#x1f973;1. 什么是bug&#xff1f;&#x1f973;2.调试有多重要&#xff1f;2.1. 我们是如何写代码的&#xff1f;2.2.调试是什么&#xff1f;2.3.调试的基本步…

uni-app 消息推送功能UniPush

uni-app 消息推送功能UniPush,这里用的是uni-app自带的UniPush1.0&#xff08;个推服务&#xff09;&#xff0c;所以只针对UniPush1.0介绍实现步骤。 建议查阅的文章&#xff1a; UniPush 1.0 使用指南[2] Unipush 常见问题[3] 当然现在已经出了UniPush2.0&#xff08;HBuilde…

如何编写一个 npm 插件?

提到写 npm 插件&#xff0c;很多没搞过的可能第一感觉觉得很难&#xff0c;无从下手&#xff0c;其实不然。 我们甚至写个简单的 console.log(hello word)&#xff0c;都是可以当成一个插件发布上去的。 其实无从下手的主要难点还是在于你的具体要做的功能逻辑&#xff0c;这…

FPGA纯verilog代码实现sobel 边缘检测,提供2套工程源码和技术支持

目录1、前言2、理论基础3、设计思路和架构4、图像输入5、RGB转灰度6、3x3卷积滑窗获取7、Sobel卷积运算8、FDMA图像缓存9、图像输出10、工程1详解&#xff1a;ov5640输入11、工程2详解&#xff1a;hdmi输入12、上板调试验证并演示13、福利&#xff1a;工程代码的获取1、前言 边…

vue 在线编辑、实时预览的代码交互组件 vue-code-view

文章目录前言实现安装依赖vue.config.js配置main.js 全局注册参数配置新建vue单文件组件库混合使用错误处理前言 vue-code-view是一个基于 vue 2.x、轻量级的代码交互组件&#xff0c;在网页中实时编辑运行代码、预览效果的代码交互组件。 官方手册&#xff1a; Vue Code Vie…

LeetCode 25. K 个一组翻转链表

原题链接 难度&#xff1a;hard\color{red}{hard}hard 题目描述 给你链表的头节点 headheadhead &#xff0c;每 kkk 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 kkk 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 kkk 的整数倍&#…