改造哈希表,封装unordered_map和unordered_set

正文开始前给大家推荐个网站,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。

unordered_map是存的是pair是K,V型的,而unordered_set是K型的,里面只存一个值,那我们如何利用一个数据结构将他们都封装出来呢?

我们知道哈希表我们实现的是存pair的,我们可以使用最笨的方法直接复制一份,把存pair的改为存Key的,但是我们可以参考一下大佬的做法,大佬直接把存的东西弄成一个模版参数,这个东西具体存的啥由用户来决定,用户传什么就存什么,所以改造后的哈希表的第二个类型模版参数就是我们要存的类型!

template <class T>
struct HashNode
{
	T _data;
	HashNode* _next;

	HashNode(const T& data)
		: _data(data)
		, _next(nullptr)
	{}
};
template <class K, class V,class KeyOfT, class Hash = HashFunc<K>>
class HashTable
{
	typedef HashNode<V> Node;
private:
	KeyOfT kt;
	vector<Node*> _tables;
	size_t _n = 0;
	Hash hs;
};

我们可以看到V是什么类型,那么这个哈希表中存的就是什么,但是会有下一个问题,我们在取余时,不管是unordered_map还是unordered_set都是对Key取余,但是这里我们不知道他是Key还是pair,那怎么办呢?

我们可以通过仿函数解决这个问题,我们每个需要用Key计算的地方都走一层仿函数,然后unordered_set的就直接返回key就行,unordered_map则需要返回pair的first。我们会看到上面的结果多了个KeyOfT的模版,这个就是返回Key的仿函数。

unordered_map

template<class K, class V>
class unordered_map
{
	struct MapKOfT
	{
		const K& operator()(const pair<const K, V>& kv)
		{
			return kv.first;
		}
	};
	private:
	hash_bucket::HashTable<K, pair<K,V>,MapKOfT> _ht;
};

unordered_set

template<class K>
class unordered_set
{
	struct SetKOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
		private:
	hash_bucket::HashTable<K, K,SetKOfT> _ht;
};

至此我们最简单的框架就搭建出来了。需要注意的是所有需要用Key的地方都要走一层仿函数。
插入删除什么的直接复用哈希表的就可以,就下来主要就是实现迭代器。

迭代器

迭代器的结构应该是什么样子的?
节点的指针肯定是必须的,但是如果我们当前的桶走完了,如何++到下一个桶呢?
所以我们需要这张哈希表,用来找当前桶走完以后的下一个桶。这里不传这张哈希表也是可以的,因为我们的目的是找下一个桶,所以把哈希表中的vector传过来也是可以的。

那么迭代器如何++呢?

如果他的下一个节点是空,那么就说明这个桶走完了,我们需要找下一个桶,所以我们需要当前的位置,所以我们可以直接把当前桶的位置传过来,也可以当场计算桶的位置,这两种方法都是可以的,但是如果这张表走完了还没找到下一个桶,那就说明这张表走完了,我们直接把节点的指针改为nullptr即可。
如果它的下一个节点不为空,那直接让它等于它的next即可。

const的迭代器我们可以和之前一样,直接用两个模版参数来决定它是普通迭代器还是const迭代器。

template <class K, class V,class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>
struct __HTIterator
{
	typedef HashNode<V> Node;
	typedef __HTIterator<K, V,Ref,Ptr, KeyOfT, Hash> Self;

	Node* _node;
	const HashTable<K, V, KeyOfT, Hash>* _pht;
	size_t hashi;
	__HTIterator(Node* node,const HashTable<K, V, KeyOfT, Hash>* pht,size_t i)
		: _node(node)
		, _pht(pht)
		, hashi(i)
	{}


	Self operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			++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;
	}

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

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

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

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

但是这里会有一个相互依赖的问题,就是哈希表需要用迭代器,迭代器需要用哈希表,如果哈希表在前面我们就需要前置声明一下迭代器,迭代器同理,我们需要在前面声明一个哈希表,但是解决完这个问题以后还存在一个问题,就是哈希表中的vector是私有成员,迭代器不能直接访问,所以我们需要把迭代器声明为哈希表的友元。
在这里插入图片描述

在这里插入图片描述
把迭代器实现好以后,接下来就是解决Key不能修改的问题。
unordeted_set和unordeted_map如何实现Key不能修改呢?
我们通过观察原码会发现unordeted_set迭代器和const迭代器都是const迭代器,它是通过这样的方式来实现的。unordeted_map是Key不能修改而Value是可以修改的,所以它的pair是pair<const K,V>它把Key设置为const,这样就能够保证Key不能修改,Value可以修改。

接下来需要实现的是unordered_map的[]重载,要实现这个重载我们就需要对哈希表的插入进行修改,它的返回值不能再是一个bool值,而是一个pair,这个pair的first是iterator迭代器,second是bool类型代表是否插入成功。改造完以后,就可以实现[]重载,但是对应容器的插入的返回值也需要变一下,[]重载主要就是存在就插入不存在就不插入,但是都会返回Val的是可以别被我们修改。

当改造完插入以后,我们会发现unordered_set的插入编译编不过,这是因为unordered_set的迭代器都是const迭代器,而哈希表的插入返回的是普通的迭代器,这里的iterator无法转化为const_iterator,所以编译错误,有两种方式可以解决,我们可以支持const迭代器转化为普通迭代器,我们也可以直接用const中的东西来构造新的普通迭代器。此时我们的封装差不多就完善了。

改造后的哈希表

namespace hash_bucket
{
	template <class T>
	struct HashNode
	{
		T _data;
		HashNode* _next;

		HashNode(const T& data)
			: _data(data)
			, _next(nullptr)
		{}
	};

	template <class K, class V, class KeyOfT, class Hash>
	class HashTable;

	template <class K, class V,class Ref, class Ptr, class KeyOfT, class Hash = HashFunc<K>>
	struct __HTIterator
	{
		typedef HashNode<V> Node;
		typedef __HTIterator<K, V,Ref,Ptr, KeyOfT, Hash> Self;

		Node* _node;
		const HashTable<K, V, KeyOfT, Hash>* _pht;
		size_t hashi;
		__HTIterator(Node* node,const HashTable<K, V, KeyOfT, Hash>* pht,size_t i)
			: _node(node)
			, _pht(pht)
			, hashi(i)
		{}


		Self operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				++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;
		}

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

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

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

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

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

		template <class K, class V,class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct __HTIterator;

	public:
		typedef __HTIterator<K, V, V&, V*, KeyOfT, Hash> iterator;
		typedef __HTIterator<K, V, const V&,const V*,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();
		}

		const_iterator end() const
		{
			return const_iterator(nullptr, this, -1);
		}
		HashTable()
		{
			_tables.resize(10);
		}

		~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;
			}
		}
		pair<iterator,bool> Insert(const V& data)
		{
			iterator ret = Find(kt(data));
			if (ret!=end())
			{
				return make_pair(ret,false);
			}

			if (_n == _tables.size())
			{
				//需要扩容
				vector<Node*> newtables;
				newtables.resize(2 * _tables.size());

				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;
						size_t hashi = hs(kt(cur->_data))% newtables.size();
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

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

				_tables.swap(newtables);
			}

			size_t hashi = hs(kt(data)) % _tables.size();
			Node* cur = new Node(data);
			cur->_next = _tables[hashi];
			_tables[hashi] = cur;
			_n++;
			return make_pair(iterator(cur,this,hashi), true);
		}

		//__HTIterator<K, V, V&, V*, KeyOfT, Hash>
		// __HTIterator<K, V,Ref,Ptr, KeyOfT, Hash>
		iterator Find(const K& k)
		{
			size_t hashi = hs(k) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kt(cur->_data) == k)
				{
					return iterator(cur,this,hashi);
				}

				cur = cur->_next;
			}

			return end();
		}

		bool Erase(const K& k)
		{
			size_t hashi = hs(k) % _tables.size();

			Node* cur = _tables[hashi];
			Node* prev = nullptr;
			while (cur)
			{
				if (cur->_kv.first == k)
				{
					if (prev==nullptr)
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				cur = cur->_next;
			}

			return false;
		}
	private:
		KeyOfT kt;
		vector<Node*> _tables;
		size_t _n = 0;
		Hash hs;
	};
}

封装的unordered_map

template<class K, class V>
	class unordered_map
	{
		struct MapKOfT
		{
			const K& operator()(const pair<const K, V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKOfT>::iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKOfT>::const_iterator const_iterator;

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

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

		const_iterator begin() const
		{
			return _ht.begin();
		}

		const_iterator end() const
		{
			return _ht.end();
		}

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

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));

			return ret.first->second;
		}
	private:
		hash_bucket::HashTable<K, pair<const K,V>,MapKOfT> _ht;
	};

封装的unordered_set

template<class K>
class unordered_set
{
	struct SetKOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	typedef typename hash_bucket::HashTable<K, K, SetKOfT>::const_iterator iterator;
	typedef typename hash_bucket::HashTable<K, K, SetKOfT>::const_iterator const_iterator;

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

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

	pair<iterator, bool> insert(const K& key)
	{
		auto ret = _ht.Insert(key);
		return make_pair(iterator(ret.first._node, ret.first._pht,ret.first.hashi), ret.second);
	}

private:
	hash_bucket::HashTable<K, K,SetKOfT> _ht;
};

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

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

相关文章

vsftp 使用虚拟用户 —— 筑梦之路

很久之前写过一遍安装vsftp的文章&#xff1a; CentOS 7 vsftpd服务器搭建记录——筑梦之路-CSDN博客 安装一条命令就可以搞定&#xff0c;这里不再赘述。 配置vsftpd.conf # /etc/vsftpd/vsftpd.conf文件修改以下配置#不允许匿名用户认证 anonymous_enableNO #NO表示所有用…

从 0 开始创建 SpringBoot 项目

从 0 开始创建 SpringBoot 项目 从 0 开始创建 SpringBoot 项目环境准备创建项目项目目录结构及说明编写代码参考 从 0 开始创建 SpringBoot 项目 环境准备 操作系统&#xff1a;Windows 10IDE&#xff1a;IntelliJ IDEA 2023.3.1Java 版本&#xff1a;jdk1.8 工具网盘链接&…

记一次挖矿病毒的溯源

ps&#xff1a;因为项目保密的原因部分的截图是自己在本地的环境复现。 1. 起因 客户打电话过来说&#xff0c;公司web服务异常卡顿。起初以为是web服务缓存过多导致&#xff0c;重启几次无果后觉得可能是受到了攻击。起初以为是ddos攻击&#xff0c;然后去查看web服务器管理…

【数据结构c实现】顺序表实现

文章目录 线性表线性表的顺序实现顺序表结构顺序表初始化增配空间Inc打印顺序表show_list线性表长度length尾部插入push_back头部插入push_front尾部删除pop_back头部删除pop_front按位置插入insert_pos按值查找find按位置删除delete_pos按值删除delete_val排序sort(冒泡&#…

【设计模式--行为型--观察者模式】

设计模式--行为型--观察者模式 观察者模式定义结构案例优缺点使用场景JDK中提供的实现例&#xff1a;警察抓小偷 观察者模式 定义 又被成为发布订阅模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生…

6.23删除二叉搜索树中的节点(LC450-M)

算法&#xff1a; 一共有五种可能的情况&#xff1a; 第一种情况&#xff1a;没找到删除的节点&#xff0c;遍历到空节点直接返回了找到删除的节点 第二种情况&#xff1a;左右孩子都为空&#xff08;叶子节点&#xff09;&#xff0c;直接删除节点&#xff0c; 返回NULL为根…

学习笔记10——Mysql的DDL语句

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/197161.html 数据库创建&#xff1a; CREATE DATABASE books&#xff1b; CREATE DATABASE IF NOT EXISTS books;更改字符集 ALTER DATABASE books CHARACTER SET gbk;库的删…

《数据结构、算法与应用C++语言描述》- 构建哈夫曼树

哈夫曼树 完整可编译运行代码见&#xff1a;Github::Data-Structures-Algorithms-and-Applications/_29huffmanTree 定长编码与可变长编码 定长编码 每个字符都用固定长度的编码来表示。 例如假设一个文本是由字符 a、u、x 和 z 组成的字符串&#xff0c;每个字符用2位二进…

ShardingSphereJDBC简单入门

ShardingSphere 介绍ShardingSphere-JDBCSharding-Sphere-ProxyShardingSphere-Sidecar混合架构运行模式DistSQL可拔插架构ShardingSphere的发展路线 主从复制ShardingSphere-JDBC功能SQL解析SQL支持程度SQL稳定支持SQL实验性支持 MySQL不支持SQL清单分页 数据分片垂直分片水平…

不再花冤枉钱!教你怎么选知识付费平台

在当今的知识付费市场中&#xff0c;用户面临的选择越来越多&#xff0c;如何从众多知识付费平台中正确选择属于自己的平台呢&#xff1f;下面&#xff0c;我们将为您介绍明理信息科技知识付费平台相比同行的优势&#xff0c;帮助您做出明智的选择。 一、创新的技术架构&#…

docker部署go gin框架 Windows环境

目录 文章目的是什么 环境介绍 Windows 环境下 docker 部署 go gin 详细步骤 运行容器时因为挂载文件可能会出现的问题 直接部署gin&#xff08;跳过运行容器时因为挂载文件可能会出现的问题&#xff09; 文章目的是什么 假设我们学习了 go 语言&#xff0c;在 Windows(本…

C语言 简单使用qsort 比较结构体字符串大小

1.先简单调用C语言封装好的冒泡排序 #include<stdio.h> #include<stdlib.h> #include<string.h> //qsort C语言封装好的冒泡排序 可比较任何类型 struct stu{char name[20];int age; }; //用户自己写的函数。函数名字也作为函数指针使用。是qsort函数的第四…

代码随想录第三十三天(一刷C语言)|斐波那契数爬楼梯使用最小花费爬楼梯

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 动态规划步骤&#xff1a; 确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 一、斐波那契数 思路&#xff1a;参考carl文档 1、dp[i]的定义为&#xff…

PDI/Kettle-9.2.0.0-R(对应jdk1.8)源码编译问题记录及源码结构简介

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 代码结构初识基本结构&#x1f4d7;代码模块详情 ⁉️问题记录❓问题一&#xff1a;代码分支哪些是发布版本❗答&#xff1a;后缀-R的版本 ❓问题二&#xff1a;50…

猫粮哪个牌子质量好性价比高?盘点十款主食冻干猫粮品牌排行榜!

在过去的100多年里&#xff0c;猫咪主食市场一直被膨化猫粮主导。然而&#xff0c;随着猫咪频频出现猝死、失明、发育不良以及营养不良等问题&#xff0c;猫主人们开始质疑膨化粮是否最适合猫咪。于是&#xff0c;从上世纪90年代开始&#xff0c;出现了生骨肉喂养。生骨肉确实是…

[算法总结] 十大排序算法

[算法总结] 十大排序算法 简介&#xff1a; 本文首发于我的个人博客&#xff1a;尾尾部落排序算法是最经典的算法知识。因为其实现代码短&#xff0c;应该广&#xff0c;在面试中经常会问到排序算法及其相关的问题。一般在面试中最常考的是快速排序和归并排序等基本的排序算法…

代码随想录算法训练营 | day48 动态规划 198.打家劫舍,213.打家劫舍Ⅱ,337.打家劫舍Ⅲ

刷题 198.打家劫舍 题目链接 | 文章讲解 | 视频讲解 题目&#xff1a;你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被…

c YUV 转 JPEG(准备霍夫曼编码)

先取yuv 文件中一个168的块&#xff0c;跑通全流程 理解与思路&#xff1a; 1.块分割 YUV 文件分为&#xff1a;YUV444 YUV 422 YUV420。444:就是&#xff1a;12个char 有4个Y&#xff0c;4个U&#xff0c;4个 U&#xff0c;422&#xff1a;8个char 中有4个Y &#x…

Oracle MongoDB

听课的时候第一次碰到&#xff0c;可以了解一下吧&#xff0c;就直接开了墨者学院的靶场 #oracle数据库 Oracle数据库注入全方位利用 - 先知社区 这篇写的真的很好 1.判断注入点 当时找了半天没找到 看样子是找到了&#xff0c;测试一下看看 id1 and 11 时没有报错 2.判断字段…

开发人员必须掌握的几个高级命令

xargs命令 在平时的使用中,我认为 xargs 这个命令还是较为重要和方便的。我们可以通过使用这个命令,将命令输出的结果作为参数传递给另一个命令。 比如说我们想找出某个路径下以 .conf 结尾的文件,并将这些文件进行分类,那么普通的做法就是先将以 .conf 结尾的文件先找出…