C++|哈希结构封装unordered_set和unordered_map

上一篇章,学习了unordered系列容器的使用,以及哈希结构,那么这一篇章将通过哈希结构来封装unordered系列容器,来进一步的学习他们的使用以及理解为何是如此使用。其实,哈希表的封装方式和红黑树的封装方式形式上是差不多的,如果有了红黑树的封装理解经验,我相信在理解哈希封装的过程会减少负担的。当然,在使用哈希结构中采用的是更具代表的哈希桶,接下来进行封装。

一、改造哈希表

1.1模板参数列表的改造

同理模板参数列表的改造跟红黑树的改造差不多。

K:关键码类型

V:不同容器v的容器类型不同,如果是unordered_map,v代表一个键值对,如果是unordered_set,v为k

KeyOfT:因为v的类型不同,通过keyOfT取key的方式就不同 。

Hash:哈希函数仿函数对象类型,哈希函数使用除留余数法,需要将字符串类型转化为整形数字才能取模。

1.2增加哈希表迭代器操作

1.2.1哈希表迭代器框架

    //前置声明,迭代器中才能定义HashTable对象
	template<class K, class T, class KeyOfT, class hash>
	class HashTable;

	template<class K,class T,class Ref,class Ptr,class KeyOfT,class hash = HashFunc<K>>
	struct _HTIterator
	{
		typedef _HTIterator<K,T, Ref, Ptr,KeyOfT,hash> Self;//方便作为接收迭代器操作的返回值的类型
		typedef HashNode<T> Node;
		typedef Node* PNode;

		PNode _node;//定义节点指针,方便访问其成员来完成迭代器的操作
		HashTable<K, T, KeyOfT, hash>* _ht;//定义哈希表指针,方便使用其成员来完成迭代器的操作
		size_t _hashi;//记录迭代器访问的位置

		_HTIterator(PNode node, HashTable<K, T, KeyOfT, hash>* ht,size_t hashi)
			:_node(node)
			,_ht(ht)
			,_hashi(hashi)
		{}

        //....
    };

1.2.2operator*() && operator->()

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

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

1.2.3operator++()

        Self& operator++()
		{
			if (_node->_next)//下一个元素不为空
			{
				_node = _node->_next;
			}
			else
			{
				/*hash hs;
				KeyOfT oft;
				int hashi = hs(oft(this->_data) % _tables.size());*/
                //寻找下一个不为空的桶
				++_hashi;
				while(_hashi < _ht->_tables.size())
				{
					if (_ht->_tables[_hashi] != nullptr)
					{
						_node = _ht->_tables[_hashi];
						break;
					}
					++_hashi;
				}
                //到末尾还没有找到不为空的桶
				if(_hashi == _ht->_tables.size())
					_node = nullptr;
				
			}
			return *this;

		}

1.2.4operator--()

        Self operator--()
		{
			PNode cur = _ht->_tables[_hashi];
			
			//当前节点就是第一个节点
			if (cur->_next == nullptr)
			{
				//寻找上一个非空桶
				--_hashi;
				while (_hashi)
				{
					//寻找该非空桶的最后一个元素
					if (_ht->_tables[_hashi])
					{
						cur = _ht->_tables[_hashi];
						while (cur->_next)
						{
							cur = cur->_next;
						}
						_node = cur;
						break;
					}
					_hashi--;
				}

			}
			else
			{
				while (cur->_next != _node)
				{
					cur = cur->_next;
				}
				_node = cur;
			}
            return *this;
		}

1.2.5operator==() && operator!=()

		bool operator==(const Self& x)
		{
			return _node == x._node;//由于没有重复的元素,直接比较节点的地址
		}

		bool operator!=(const Self& x)
		{

			return _node != x._node;
		}

二、如何用哈希表搭配unordered_map和unordered_set(仿函数)

我们可以用两张哈希表分别封装一份unordered_map和一份unordered_set,但是这样做的效果就带来了代码冗余。为了减少代码冗余,模拟跟库保持用一张哈希表封装unordered_map和unordered_set,但是该如何做到套用一张表呢,我们来进一步分析。

 首先对于unordered_map而言,其存放的节点值是pair,而对于unordered_set存放的是key,这对于哈希表节点的实现到是没啥问题,但是对于哈希表内部的构造,是需要查询插入的位置,就需要进行比较,若将比较实现成key的比较,那么对于pair类型又该如何比较,虽然知道比较的也是pair中的key,但是如何做到既满足unordered_set中的key类型比较,又满足pair类型中的key比较,总不能干两份代码吧。这个时候,我们的仿函数又派上用场了,对于unordered_set和unordered_map中都构造一个仿函数,分别表示取到unordered_set的key,和unordered_map中pair中的key,那么哈希表中的比较,就可以换成仿函数的比较,当往unordered_set中插入元素进行比较,调用的就是unordered_set的仿函数,当往unordered_map中插入元素进行比较,调用的就是unordered_map的仿函数从而达到回调。用一张图来进行表示,如图:

三、哈希表封装unordered_map和unordered_set(简易版)

3.1哈希表的实现(HashTable.h)

//哈希桶/链地址法
namespace Hash_Bucket
{
	template<class T>
	struct HashNode
	{
		HashNode(const T& data)
			:_next(nullptr)
			,_data(data)
		{}
		HashNode<T>* _next;
		T _data;
	};

	//前置声明,迭代器中才能定义HashTable对象
	template<class K, class T, class KeyOfT, class hash>
	class HashTable;

	template<class K,class T,class Ref,class Ptr,class KeyOfT,class hash = HashFunc<K>>
	struct _HTIterator
	{
		typedef _HTIterator<K,T, Ref, Ptr,KeyOfT,hash> Self;
		typedef HashNode<T> Node;
		typedef Node* PNode;

		PNode _node;
		HashTable<K, T, KeyOfT, hash>* _ht;
		size_t _hashi;
		_HTIterator(PNode node, HashTable<K, T, KeyOfT, hash>* ht,size_t hashi)
			:_node(node)
			,_ht(ht)
			,_hashi(hashi)
		{}

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				/*hash hs;
				KeyOfT oft;
				int hashi = hs(oft(this->_data) % _tables.size());*/

				++_hashi;
				while(_hashi < _ht->_tables.size())
				{
					if (_ht->_tables[_hashi] != nullptr)
					{
						_node = _ht->_tables[_hashi];
						break;
					}
					++_hashi;
				}
				if(_hashi == _ht->_tables.size())
					_node = nullptr;
				
			}
			return *this;

		}



		Self operator--()
		{
			PNode cur = _ht->_tables[_hashi];
			
			//当前节点就是第一个节点
			if (cur->_next == nullptr)
			{
				//寻找上一个非空桶
				--_hashi;
				while (_hashi)
				{
					//寻找该非空桶的最后一个元素
					if (_ht->_tables[_hashi])
					{
						cur = _ht->_tables[_hashi];
						while (cur->_next)
						{
							cur = cur->_next;
						}
						_node = cur;
						break;
					}
					_hashi--;
				}

			}
			else
			{
				while (cur->_next != _node)
				{
					cur = cur->_next;
				}
				_node = cur;
			}
			return *this;
		}

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

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

		bool operator==(const Self& x)
		{
			return _node == x._node;//由于没有重复的元素,直接比较节点的地址
		}

		bool operator!=(const Self& x)
		{

			return _node != x._node;
		}

	};

	template<class K,class T,class KeyOfT,class hash>
	class HashTable
	{
		//声明友元,迭代器方可访问该类中的私有成员
		template<class K, class T, class Ref, class Ptr, class KeyOfT,class hash>
		friend struct _HTIterator;
	public:
		typedef _HTIterator<K, T,T&, T*, KeyOfT,hash> iterator;
		typedef _HTIterator<K, T, const T&, const T*, KeyOfT, hash> const_iterator;

		typedef HashNode<T> Node;
		typedef Node* PNode;

		HashTable(size_t size = 10)
		{
			_tables.resize(size);
		}

		iterator begin()
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i] != nullptr)
					return iterator(_tables[i], this, i);
			}
			return end();

		}
		iterator end()
		{
			return iterator(nullptr,this,-1);
		}

		const_iterator begin() const
		{
			for (int i = 0; i < _tables.size(); i++)
			{
				if (_tables[i] != nullptr)
					return iterator(_tables[i], this, i);
			}
			return end();

		}
		const_iterator end() const
		{
			return iterator(nullptr, this, -1);
		}

		iterator Find(const K& key)
		{
			KeyOfT oft;
			hash hs;
			int hashi = hs(key) % _tables.size();
			
			
			PNode cur = _tables[hashi];
			while (cur)
			{
				if(hs(oft(cur->_data)) == hs(key))
					return iterator(cur, this, hashi);

				cur = cur->_next;
			}
			return iterator(nullptr, this, -1);
		}

		pair<iterator,bool> Insert(T data)
		{
			hash hs;
			KeyOfT oft;
			iterator it = Find(oft(data));
			if (it != end())
				return make_pair(it,false);



			//扩容
			if (_n == _tables.size())
			{
				vector<PNode> _newHT(_tables.size()*2);
				
				for (int i = 0; i < _tables.size(); i++)
				{
					
					PNode cur = _tables[i];
					while (cur)
					{
						PNode next = cur->_next;

						int hashi = hs(oft(_tables[i]->_data)) % _newHT.size();

						//元素一直在变,所以不能用_tables[i]做代表
						/*_tables[i]->_next = nullptr;
						_newHT[hashi] = _tables[i];*/

						cur->_next = nullptr;
						_newHT[hashi] = cur;
						
						cur = next;
					}
					_tables[i] = nullptr;
				}
                _tables.swap(_newHT);
			}
			//头插
			PNode newnode =new Node(data);
			int hashi = hs(oft(data)) % _tables.size();
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			
			return make_pair(iterator(newnode, this, hashi),true);

		}

		bool Erase(const K& key)
		{
			KeyOfT oft;
			iterator it = Find(key);
			if (it == end())
				return false;
			hash hs;
			int hashi = hs(key) % _tables.size();
			PNode cur = _tables[hashi];
			PNode parent = nullptr;
			while (hs(oft(cur->_data)) != hs(key))
			{
				parent = cur;
				cur = cur->_next;
			}
			if (parent)
			{
				delete cur;
				parent->_next = nullptr;
			}
			else
			{
				delete cur;
				_tables[hashi] = nullptr;
			}
			return true;
		}

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

					delete cur;

					cur = next;
				}
				_tables[i] = nullptr;
			}
		}



		//另外加的,为了测试用
		void Some()
		{
			size_t bucketSize = 0;
			size_t maxBucketLen = 0;
			size_t sum = 0;
			double averageBucketLen = 0;

			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					++bucketSize;
				}

				size_t bucketLen = 0;
				while (cur)
				{
					++bucketLen;
					cur = cur->_next;
				}

				sum += bucketLen;

				if (bucketLen > maxBucketLen)
				{
					maxBucketLen = bucketLen;
				}
			}

			averageBucketLen = (double)sum / (double)bucketSize;

			printf("all bucketSize:%d\n", _tables.size());
			printf("bucketSize:%d\n", bucketSize);
			printf("maxBucketLen:%d\n", maxBucketLen);
			printf("averageBucketLen:%lf\n\n", averageBucketLen);
		}
	private:
		vector<HashNode<T>*> _tables;
		size_t _n = 0;
	};

}

3.2unordered_map的模拟实现(My_Unordered_Map.h)

#pragma once

#include "HashTable.h"


namespace Hash_Bucket
{
	template<class K,class V,class Hash = HashFunc<K>>
	class unordered_map
	{
	public:
		struct KeyOfTMap
		{
			const K& operator()(const pair<const K, V>& data)
			{
				return data.first;
			}
		};
		typedef typename HashTable<K, pair<const K,V>, KeyOfTMap, Hash>::iterator iterator;
		typedef typename HashTable<K, pair<const K,V>, KeyOfTMap, Hash>::const_iterator const_iterator;


		iterator begin()
		{
			return _hs.begin();
		}
		iterator end()
		{
			return _hs.end();
		}

		const_iterator begin() const
		{
			return _hs.begin();
		}
		const_iterator end() const
		{
			return _hs.end();
		}

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

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

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

	private:
		HashTable<K, pair<const K,V>, KeyOfTMap, Hash> _hs;
	};

	void test_map()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", ""));
		dict.insert(make_pair("string", ""));
		dict.insert(make_pair("insert", ""));

		for (auto& kv : dict)
		{
			//kv.first += 'x';
			kv.second += 'x';

			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;

		string arr[] = { "你", "  ","好", "  ", "吗", "  ", "你", "吃", "  ", "了", "饭", "吗", "?" };
		unordered_map<string, int> count_map;
		for (auto& e : arr)
		{
			count_map[e]++;
		}

		for (auto& kv : count_map)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
	}
}

3.3unordered_set的模拟实现(My_Unordered_Set.h)

#pragma once

#include "HashTable.h"


namespace Hash_Bucket
{
	template<class K,class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		struct KeyOfTSet
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename HashTable<K, const K, KeyOfTSet, Hash>::iterator iterator;
		typedef typename HashTable<K, const K, KeyOfTSet, Hash>::const_iterator const_iterator;


		iterator begin()
		{
			return _hs.begin();
		}
		iterator end()
		{
			return _hs.end();
		}

		const_iterator begin() const
		{
			return _hs.begin();
		}
		const_iterator end() const
		{
			return _hs.end();
		}
		
		pair<iterator, bool> insert(const K& key)
		{
			return _hs.Insert(key);
		}


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

		iterator find(const K& key)
		{
			return _hs.Find(key);
		}
		void some()
		{
			_hs.Some();
		}
	private:
		HashTable<K, const K, KeyOfTSet, Hash> _hs;
	};

	void test_set()
	{
		
		unordered_set<int> us;
		us.insert(5);
		us.insert(15);
		us.insert(52);
		us.insert(3);

		unordered_set<int>::iterator it = us.begin();
		while (it != us.end())
		{
			//*it += 5;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
		us.some();
	}

}

3.4测试(test.cpp)

#include "My_Unordered_Map.h"
#include "My_Unordered_Set.h"

int main()
{

	Hash_Bucket::test_map();
	Hash_Bucket::test_set();

	return 0;
}

输出结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

极坐标下的牛拉法潮流计算9节点MATLAB程序

微❤关注“电气仔推送”获得资料&#xff08;专享优惠&#xff09; 潮流计算&#xff1a; 潮流计算是根据给定的电网结构、参数和发电机、负荷等元件的运行条件&#xff0c;确定电力系统各部分稳态运行状态参数的计算。通常给定的运行条件有系统中各电源和负荷点的功率、枢纽…

Python | Leetcode Python题解之第145题二叉树的后序遍历

题目&#xff1a; 题解&#xff1a; class Solution:def postorderTraversal(self, root: TreeNode) -> List[int]:def addPath(node: TreeNode):count 0while node:count 1res.append(node.val)node node.righti, j len(res) - count, len(res) - 1while i < j:res…

vue2前置路由守卫中使用this.$store.state报错解决

1、问题描述&#xff1a;在前置路由守卫逻辑中&#xff0c;要更改vuex中的store的state状态&#xff0c;使用常规的this.$store.state报错 2、问题原因&#xff1a; 在vue2是vueRouter前置路由守卫中&#xff0c;this关键字并不会指向vue实例&#xff0c;因此不能使用this.$st…

【CHIP】LTC2991 读取温度电压电流 调试实例

文章目录 0. ENV1. LTC2991 数据说明1. 数据计算公式2. 寄存器概述1. 管脚使能寄存器2. 芯片使能寄存器 2. 软件实现1. 概述2. 源码(部分)3. 参考log 0. ENV 软件系统&#xff1a;略 LTC2991&#xff1a;VCC3.3 温度&#xff1a;温控接v1-v2 / v2-v3 / … (双端采样)电压&#…

【LLM】快速了解Dify 0.6.10的核心功能:知识库检索、Agent创建和工作流编排(二)

【LLM】快速了解Dify 0.6.10的核心功能&#xff1a;知识库检索、Agent创建和工作流编排&#xff08;二&#xff09; 文章目录 【LLM】快速了解Dify 0.6.10的核心功能&#xff1a;知识库检索、Agent创建和工作流编排&#xff08;二&#xff09;一、创建一个简单的聊天助手&#…

nmap工具使用

nmap是一款渗透端口扫描测试工具。它不单单可以进行端口扫描&#xff0c;还可以扫描漏洞、服务器信息等等。是一款十分强大的扫描工具&#xff0c;可以在windows、mac、Linux上运行。 下载 官网地址: https://nmap.org/download.html 我备份的地址:https://download.csdn.net…

[大模型]LLaMA3-8B-Instruct WebDemo 部署

环境准备 在 autodl 平台中租赁一个 3090 等 24G 显存的显卡机器&#xff0c;如下图所示镜像选择 PyTorch-->2.1.0-->3.10(ubuntu20.04)-->12.1 接下来打开刚刚租用服务器的 JupyterLab&#xff0c;并且打开其中的终端开始环境配置、模型下载和运行 demo。 pip 换源…

spring boot3登录开发-邮箱登录/注册接口实现

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 &#x1f30a;山高路远&#xff0c;行路漫漫&#xff0c;终有归途 目录 写在前面 上文衔接 内容简介 功能分析 所需依赖 邮箱验证登录/注册实现 1.创建交互对象 2.登录注册业务逻辑实…

【教学类-12-11】20240612通义万相-动物图片连连看(A4一页3套)

背景需求&#xff1a; 前期用midjounery下载了一些动物头饰图片 【教学类-36-02】20230625动物头饰制作1.0&#xff08;midjounery动物简笔画四图&#xff09;一页一种动物_英语头饰动物的制作图片-CSDN博客文章浏览阅读471次。【教学类-36-02】20230625动物头饰制作1.0&…

万界星空科技SMT行业MES系统功能

在现代制造业中&#xff0c;SMT智能车间MES系统是一种全自动化的生产管理系统&#xff0c;用于监控和控制整个SMT生产流程。它通过监控SMT设备的运行状态、实时追踪生产数据&#xff0c;并与其他系统进行实时数据交换&#xff0c;以提高生产效率、降低生产成本。MES系统采用先进…

基于电压矢量变换的锁相环simulink建模与仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 5.完整工程文件 1.课题概述 基于电压矢量变换的锁相环simulink建模与仿真&#xff0c;这个模型的基本构架如下所示&#xff1a; 2.系统仿真结果 由图中锁相结果可以看出&#xff0c;利用新型锁相环技术在…

基于构件开发模型-系统架构师(八)

1、把应用程序中应用最频繁的那部分核心程序作为评价计算机性能的标准程序&#xff0c;称为&#xff08;&#xff09;程序。 A仿真测试 B核心测试 C基准测试 D标准测试 解析&#xff1a; 系统测试最核心的部分内容&#xff0c;基准测试。 2、运用信息技术进行知识的挖掘和…

Go使用https

一、服务端 1. 生成私钥和证书 安装OpenSSL windows安装OpenSSL生成CA证书创建证书 以上两个步骤&#xff0c;参考&#xff1a;Go http2 和 h2c 2. 代码 package mainimport ("log""net/http""time""golang.org/x/net/http2" )co…

笔记 | 软件工程06-2:软件设计-软件体系结构设计

1 软件体系结构的概念 1.1 软件体系结构的设计元素 1.2 不同的抽象层次 1.3 软件体系结构的不同视图 1.3.1 软件体系结构的逻辑视图&#xff1a;包图 1.3.2 软件体系结构的逻辑视图&#xff1a;构件图 1.3.3 软件体系结构的开发视图 1.3.4 软件体系结构的部署视图 1.3.4.1 描述…

【docker】compose 使用 .env 文件

在 Docker Compose 中&#xff0c;你可以使用 .env 文件来定义环境变量&#xff0c;这些变量可以在 docker-compose.yml 文件中被引用。这允许你轻松地管理配置&#xff0c;而不需要硬编码值到你的 Compose 文件中。 以下是如何在 Docker Compose 中使用 .env 文件的步骤&…

【DevOps】 什么是容器 - 一种全新的软件部署方式

目录 引言 一、什么是容器 二、容器的工作原理 三、容器的主要特性 四、容器技术带来的变革 五、容器技术的主要应用场景 六、容器技术的主要挑战 七、容器技术的发展趋势 引言 在过去的几十年里,软件行业经历了飞速的发展。从最初的大型机时代,到后来的个人电脑时代,…

Java面试题--JVM大厂篇之掌握JVM性能优化:选择合适的垃圾回收器

掌握JVM性能优化&#xff1a;选择合适的垃圾回收器 引言: ​  在Java开发中&#xff0c;性能优化往往是提高应用稳定性和用户体验的关键所在。而垃圾回收器的选择和优化&#xff0c;是JVM性能调优的核心环节。如何在众多垃圾回收器中选出适合自己应用需求的那一个&#xff1…

AXI Quad SPI IP核配置详解

AXI Quad SPI IP核&#xff08;Quad Serial Peripheral Interface&#xff09;是一个提供串行接口连接SPI从设备的解决方案&#xff0c;它支持Standard&#xff08;单线路&#xff09;、Dual&#xff08;双线路&#xff09;、Quad&#xff08;四线路&#xff09;模式&#xff0…

luogu-P10570 [JRKSJ R8] 网球

题目传送门&#xff1a; [JRKSJ R8] 网球 - 洛谷https://www.luogu.com.cn/problem/P10570 解题思路 数学问题&#xff0c;暴力这个范围会超时。 首先&#xff0c;找出这两个数的最大公因数&#xff0c;将这两个数分别除以最大公因数&#xff0c;则这两个数互质&#xff0c;判…

启明智显工业级HMI芯片Model3功耗特性分享

前言&#xff1a; 【启明智显】专注于HMI&#xff08;人机交互&#xff09;及AIoT&#xff08;人工智能物联网&#xff09;产品和解决方案的提供商&#xff0c;我们深知彩屏显示方案在现代物联网应用中的重要性。为此&#xff0c;我们一直致力于为客户提供彩屏显示方案相关的技…