C++:采用哈希表封装unordered_map和unordered_set

news2024/11/19 16:33:40

目录

一. 如何使用一张哈希表封装unordered_map和unordered_set

二. 哈希表迭代器的实现

2.1 迭代器成员变量及应当实现的功能

2.2 operator++函数

2.3 operator*和operator->函数

2.4 operator!=和operator==函数

2.5 begin()和end()

2.6哈希表迭代器实现代码

三. unordered_map和unordered_set的实现

附录:哈希表封装unorder_map和unordered_set完整版代码


一. 如何使用一张哈希表封装unordered_map和unordered_set

采用一张哈希表封装unordered_map和unordered_set的方法与采用一颗红黑树封装map和set的模型基本完全一样。

哈希表类HashTable有四个模板参数,分别为:键值Key的数据类型K、与键值Key匹配的数据的类型V、哈希仿函数函数类Hash以及用于提取键值的仿函数类KeyOfV。

  • 当哈希表封装用于unordered_map时,K-V构成键值对,作为哈希表中存储的数据的类型。当用于封装unordered_set时,V与K相同,哈希表中每个节点存储一个类型为K的数据,此时K和V实例化后的类型相同。
  • Hash为哈希仿函数,用于将键值Key转换为size_t(如string类型的键值,就需要相应的算法将其表征为size_t),以便通过哈希函数计算其存储的位置。
  • KeyOfV用于在提取键值Key,在unordered_map下,提取键值对的first,在unordered_set下,直接取Key。
图1.1 HashTable与unordered_map和unordered_set模板参数之间的关系

二. 哈希表迭代器的实现

2.1 迭代器成员变量及应当实现的功能

应当有两个成员变量:指向当前节点的指针_cur和哈希表类对象指针_ptable。成员函数及所实现的功能应当包括:

  • operator++函数:哈希表下一个有效节点的迭代器。
  • operator->和operator*,用于访问节点数据_data。
  • operator==和operator!=,用于判断两个迭代器是否表示同一个哈希表节点。

2.2 operator++函数

函数功能为找哈希表下一个有效位置,并返回那个位置的迭代器。要分两种情况讨论:

  1. 如果_cur->_next节点不是nullptr,则表明当前的_cur不是目前桶的最下面的节点,直接将_cur更新为_cur->next,然后返回*this即可。
  2. 如何_cur->next是nullptr,则表明下一个有效节点不在当前哈希桶,要么没有下一个有效节点,如果有就是_hashTable某个桶最上面位置的节点。因此只需向后遍历_hashTable[i],遇到不为空或结尾即可。
图2.1 operator++情况1
图2.2 operator++情况2

代码2.1:迭代器operator++函数

	//Self&表示迭代器本身类型的引用
    //迭代器自加函数
	Self& operator++()
	{
		//分两种情况讨论:cur所在的桶的next是否为空
		
		//_cur的下一个节点不为空
		if (_cur->_next)
		{
			_cur = _cur->_next;
			return *this;
		}
		else   //_cur的_next节点为空,++要去到其他桶
		{
			Hash hash;
			KeyOfV kofv;
			size_t hashi = hash(kofv(_cur->_data)) % _ptable->_hashTable.size();

			for (size_t i = hashi + 1; i < _ptable->_hashTable.size(); ++i)
			{
				_cur = _ptable->_hashTable[i];
				if (_cur)
				{
					return *this;
				}
			}

			return *this;
		}
	}

2.3 operator*和operator->函数

这两个函数都是用于返回节点数据的,不同在于:operator*返回节点数据_data本身的引用,operator->返回_data的地址。

代码2.2:operator*和operator->函数

	//V为哈希表存储的数据的类型
    V& operator*()
	{
		return _cur->_data;
	}

	V* operator->()
	{
		return &_cur->_data;
	}

2.4 operator!=和operator==函数

如果两个迭代器表示不用的哈希表类对象的节点位置,那么他们的_cur一定不同,因此,只需要比较两个迭代器的_cur是否相同即可。

代码2.3:operator!=和operator==函数

	//迭代器相等判断函数
	bool operator==(const Self& it)
	{
		return _cur == it._cur;
	}

	//迭代器不等判断函数
	bool operator!=(const Self& it)
	{
		return _cur != it._cur;
	}

2.5 begin()和end()

begin()表示第一个有效节点的位置,用nullptr表示最后一个有效节点后面的位置end()。

图2.3 begin()和end()

2.6哈希表迭代器实现代码

//哈希表类的声明
template<class K, class V, class Hash, class KeyOfV>
class HashTable;

template<class K, class V, class Hash, class KeyOfV>
struct __HashIterator
{
	typedef HashData<V> Node;
	typedef HashTable<K, V, Hash, KeyOfV> Table;
	typedef __HashIterator<K, V, Hash, KeyOfV> Self;

	Node* _cur;
	Table* _ptable;

	//迭代器构造函数
	__HashIterator(Node* cur, Table* ptable)
		: _cur(cur)
		, _ptable(ptable)
	{ }

	//迭代器自加函数
	Self& operator++()
	{
		//分两种情况讨论:cur所在的桶的next是否为空
		
		//_cur的下一个节点不为空
		if (_cur->_next)
		{
			_cur = _cur->_next;
			return *this;
		}
		else   //_cur的_next节点为空,++要去到其他桶
		{
			Hash hash;
			KeyOfV kofv;
			size_t hashi = hash(kofv(_cur->_data)) % _ptable->_hashTable.size();

			for (size_t i = hashi + 1; i < _ptable->_hashTable.size(); ++i)
			{
				_cur = _ptable->_hashTable[i];
				if (_cur)
				{
					return *this;
				}
			}

			return *this;
		}
	}

	V& operator*()
	{
		return _cur->_data;
	}

	V* operator->()
	{
		return &_cur->_data;
	}

	//迭代器相等判断函数
	bool operator==(const Self& it)
	{
		return _cur == it._cur;
	}

	//迭代器不等判断函数
	bool operator!=(const Self& it)
	{
		return _cur != it._cur;
	}
};

三. unordered_map和unordered_set的实现

在unordered_map和unordered_set中,会存储一个自定义类型HashTable的成员变量_ht,_ht是一张哈希表。unordered_map和unordered_set的insert、erase、find、begin、end()函数只需要复用HashTable的对应成员函数即可。

唯一需要注意的是unordered_map中的operator[]函数,其应当返回与Key匹配的Value的引用,或者插入一个键值为Key的数据。operator[]中复用哈希表insert函数,insert函数返回一键值对,这个键值对的first为原有Key的节点位置的迭代器或新插入节点的迭代器位置。

代码3.1:unordered_map的封装

	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		//从节点数据中提取键值Key的仿函数
		struct MapKeyOfValue
		{
			const K& operator()(const std::pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename HashTable<K, std::pair<K, V>, Hash, MapKeyOfValue>::iterator iterator;

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

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

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

		HashData<V>* find(const K& key)
		{
			return _ht.find(key);
		}

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

	private:
		HashTable<K, std::pair<K, V>, Hash, MapKeyOfValue> _ht;
	};

代码3.2:unordered_set的封装

	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfValue
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};

	public:
		typedef typename HashTable<K, K, Hash, SetKeyOfValue>::iterator iterator;

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

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

		std::pair<iterator, bool> insert(const K& data)
		{
			return _ht.insert(data);
		}

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

		bool erase(const K& data)
		{
			return _ht.find(data);
		}

	private:
		HashTable<K, K, Hash, SetKeyOfValue> _ht;
	};

附录:哈希表封装unorder_map和unordered_set完整版代码

//hashTable.h文件 -- 哈希表的实现
#pragma once
#include<vector>

template<class V>
struct HashData
{
	V _data;
	HashData<V>* _next;

	HashData(const V& data)
		: _data(data)
		, _next(nullptr)
	{ }
};

//仿函数 -- 用于将键值key转换为size_t类型数据
//这样可以带入哈希函数,使不同key值可以映射到不同存储位置
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

//哈希表类的声明
template<class K, class V, class Hash, class KeyOfV>
class HashTable;

template<class K, class V, class Hash, class KeyOfV>
struct __HashIterator
{
	typedef HashData<V> Node;
	typedef HashTable<K, V, Hash, KeyOfV> Table;
	typedef __HashIterator<K, V, Hash, KeyOfV> Self;

	Node* _cur;
	Table* _ptable;

	//迭代器构造函数
	__HashIterator(Node* cur, Table* ptable)
		: _cur(cur)
		, _ptable(ptable)
	{ }

	//迭代器自加函数
	Self& operator++()
	{
		//分两种情况讨论:cur所在的桶的next是否为空
		
		//_cur的下一个节点不为空
		if (_cur->_next)
		{
			_cur = _cur->_next;
			return *this;
		}
		else   //_cur的_next节点为空,++要去到其他桶
		{
			Hash hash;
			KeyOfV kofv;
			size_t hashi = hash(kofv(_cur->_data)) % _ptable->_hashTable.size();

			for (size_t i = hashi + 1; i < _ptable->_hashTable.size(); ++i)
			{
				_cur = _ptable->_hashTable[i];
				if (_cur)
				{
					return *this;
				}
			}

			return *this;
		}
	}

	V& operator*()
	{
		return _cur->_data;
	}

	V* operator->()
	{
		return &_cur->_data;
	}

	//迭代器相等判断函数
	bool operator==(const Self& it)
	{
		return _cur == it._cur;
	}

	//迭代器不等判断函数
	bool operator!=(const Self& it)
	{
		return _cur != it._cur;
	}
};

template<class K, class V, class Hash, class KeyOfV>
class HashTable
{
	typedef HashData<V> Node;

	friend struct __HashIterator<K, V, Hash, KeyOfV>;

public:
	typedef __HashIterator<K, V, Hash, KeyOfV> iterator;

	//哈希表析构函数
	~HashTable()
	{
		//依次释放每个桶(单链表节点)
		for (size_t i = 0; i < _hashTable.size(); ++i)
		{
			Node* cur = _hashTable[i];

			//while循环释放每个非空节点
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}

			_hashTable[i] = nullptr;
		}

		//_hashTable是vector成员,编译器会自动调用其析构函数
	}

	//查找哈希表第一个有效节点
	iterator begin()
	{
		for (size_t i = 0; i < _hashTable.size(); ++i)
		{
			if (_hashTable[i])
			{
				return iterator(_hashTable[i], this);
			}
		}

		return iterator(nullptr, this);
	}

	//最后一个节点的下一位置
	iterator end()
	{
		return iterator(nullptr, this);
	}

	//数据插入函数
	std::pair<iterator, bool> insert(const V& data)
	{
		//如果哈希表中已经存在kv,那么不插入数据,直接返回
		iterator ret = find(_kofv(data));
		if (ret != end())
		{
			return std::make_pair(ret, false);
		}

		//检查扩容
		//当哈希表容量为0(第一次插入数据前),或负载因子在插入数据后会大于1的情况下,对哈希表执行二倍扩容
		if (_hashTable.size() == 0 || _size == _hashTable.size())
		{
			size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();   //新容量

			//建立一个辅助新表(用于让扩容后原哈希表中数据映射到新的位置)
			std::vector<Node*> newTable;
			newTable.resize(newSize, nullptr);   //表中每个哈希桶先初始化为nullptr

			//挪动数据 -- 等价于单链表头插操作
			for (size_t i = 0; i < _hashTable.size(); ++i)
			{
				Node* cur = _hashTable[i];   //当前哈希桶头结点

				//将原来的每个节点头插到新表的对应位置
				while (cur)
				{
					size_t hashi = _hash(_kofv(cur->_data)) % newSize;

					//头插
					Node* next = cur->_next;
					cur->_next = newTable[hashi];
					newTable[hashi] = cur;

					cur = next;
				}
			}

			_hashTable.swap(newTable);
		}

		Node* insertNode = new Node(data);   //新插入的节点
		size_t hashi = _hash(_kofv(data)) % _hashTable.size();   //计算新节点在哪个哈希桶

		//节点插入
		insertNode->_next = _hashTable[hashi];
		_hashTable[hashi] = insertNode;
		++_size;

		return std::make_pair(iterator(insertNode, this), true);
	}
	
	//数据查找函数
	iterator find(const K& key)
	{
		//表容量为0时,一定找不到任何数据
		//为了避免除0错误,直接返回
		if (_hashTable.size() == 0)
		{
			return iterator(nullptr, this);
		}

		size_t hashi = _hash(key) % _hashTable.size();   //确定key应该在哪个哈希桶
		Node* cur = _hashTable[hashi];

		//查找hashi对应的哈希桶是否存在key
		while (cur)
		{
			if (_kofv(cur->_data) == key)
			{
				return iterator(cur, this);
			}
			cur = cur->_next;
		}

		return iterator(nullptr, this);
	}

	//数据删除函数
	bool erase(const K& key)
	{
		//避免除0错误
		if (_hashTable.size() == 0)
		{
			return false;
		}

		size_t hashi = _hash(key) % _hashTable.size();
		Node* prev = nullptr;
		Node* cur = _hashTable[hashi];

		//找到键值为key的节点删除
		//等价于单链表节点删除操作 -- 分删除头结点和中间节点两种情况来讨论
		while (cur)
		{
			//找到了要删除的节点
			if (_kofv(cur->_data) == key)
			{
				//删除头结点
				if (prev == nullptr)
				{
					_hashTable[hashi] = cur->_next;
					free(cur);
					cur = nullptr;
				}
				else  //删除中间节点
				{
					prev->_next = cur->_next;
					free(cur);
					cur = nullptr;
				}

				return true;
			}

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

		return false;
	}

private:
	size_t _size = 0;
	Hash _hash;
	KeyOfV _kofv;
	std::vector<Node*> _hashTable;
};



//UnorderedMap.h文件 -- unordered_map的封装
#pragma once
#include "hashTable.h"

namespace zhang
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
		//从节点数据中提取键值Key的仿函数
		struct MapKeyOfValue
		{
			const K& operator()(const std::pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename HashTable<K, std::pair<K, V>, Hash, MapKeyOfValue>::iterator iterator;

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

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

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

		HashData<V>* find(const K& key)
		{
			return _ht.find(key);
		}

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

	private:
		HashTable<K, std::pair<K, V>, Hash, MapKeyOfValue> _ht;
	};
}



//UnorderedSet.h文件 -- unordered_set的封装
#pragma once
#include "hashTable.h"

namespace zhang
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
		struct SetKeyOfValue
		{
			const K& operator()(const K& data)
			{
				return data;
			}
		};

	public:
		typedef typename HashTable<K, K, Hash, SetKeyOfValue>::iterator iterator;

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

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

		std::pair<iterator, bool> insert(const K& data)
		{
			return _ht.insert(data);
		}

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

		bool erase(const K& data)
		{
			return _ht.find(data);
		}

	private:
		HashTable<K, K, Hash, SetKeyOfValue> _ht;
	};
}

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

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

相关文章

渗透测试--6.2.mdk3攻击wifi

前言 本次依然使用Kali虚拟机系统&#xff0c;win11主机&#xff0c;网卡Ralink 802.11 配合mdk3进行wifi伪造、连接设备查看、解除认证攻击。本次实验只用于学习交流&#xff0c;攻击目标为自家的手机热点&#xff0c;请勿违法使用&#xff01; 目录 前言 1.Deauth攻击原…

Electron简介、安装、实践

本文中的所有代码均存放在https://github.com/MADMAX110/my-electron-app Electron是什么&#xff1f; Electron是一个开源的框架&#xff0c;可以使用JavaScript, HTML和CSS来构建跨平台的桌面应用程序。Electron的核心是由Chromium和Node.js组成&#xff0c;它们分别提供了渲…

【springboot 开发工具】接口文档我正在使用它生成,舒坦

前言 先来描述下背景&#xff1a;由于新公司业务属于自研产品开发&#xff0c;但是发现各产品业务线对于接口文档暂时还是通过集成Swagger来维护&#xff0c;准确来说是knife4j&#xff08;Swagger的增强解决方案&#xff09;。但是对于5年的后端开发老说&#xff0c;早就厌倦…

Java-线程安全的四个经典案例和线程池

单例模式 有些对象&#xff0c;在一个程序中应该只有唯一 一个实例&#xff08;光靠人保证不靠谱 借助语法来保证&#xff09; 就可以使用单例模式 在单例模式下 对象的实例化被限制了 只能创建一个 多了的也创建不了 单例模式分为两种&#xff1a;饿汉模式和懒汉模式 饿汉模式…

[Java基础]—SpringBoot

Springboot入门 Helloworld 依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.4.RELEASE</version> </parent><dependencies><depend…

软件测试基础知识整理(四)- 软件开发模型、测试过程模型

目录 一、软件开发模型 1.1 瀑布模型 1.1.1 特点 1.1.2 优缺点 1.2 快速原型模型&#xff08;了解&#xff09; 1.2.1 特点 1.2.2 优缺点 1.3 螺旋模型&#xff08;了解&#xff09; 1.3.1 特点 1.3.2 优缺点 二、测试过程模型 2.1 V模型&#xff08;重点&#xff…

LeetCode_29. 两数相除

目录 题目描述 思路分析 我的题解 题目描述 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff…

8个免费的高质量UI图标大全网站

UI图标素材是设计师必不可少的设计元素。 高质量的UI图标会让设计师的设计效率事半功倍。 本文分享8个免费的高质量UI图标大全网站。 即时设计资源社区 即时设计资源广场中精选了多款专业免费的UI图标设计资源&#xff0c;无需下载即可一键保存源文件&#xff0c;同时还提供…

深入浅析Linux Perf 性能分析工具及火焰图

Perf Event 子系统 Perf 是内置于 Linux 内核源码树中的性能剖析&#xff08;profiling&#xff09;工具。它基于事件采样的原理&#xff0c;以性能事件为基础&#xff0c;支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析。可用于性能瓶颈的查找与热点代码的定位…

Maven PKIX path building failed 错误提示

最近公司的项目突然出现了下面的提示。 PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target -> [Help 2]问题和解决 出现上面的提示的问题是因为 SSL 签名的问题。 …

经典面试题:理解Cookie和Session之间的区别

文章目录 一、Cookie概念先知1、Cookie是什么&#xff1f;2、Cookie从哪里来&#xff1f;3、Cookie要存到哪里去&#xff1f;4、Cookie是存在哪里的&#xff1f;5、浏览器是如何通过Cookie来记录的&#xff1f;6、Cookie的过期时间有什么用&#xff1f; 二、见见Cookie三、会话…

软件设计师考试笔记,已通过

目录 系统可靠度 外部实体 内聚类型 编译过程 逆波兰式 前驱图 scrum框架模型 编译和解释 有限自动机 聚簇索引和非聚簇索引 二叉树的前序,中序,后序遍历 动态规划贪心算法 算法 01背包问题 系统可靠度 1. 串联部件可靠度 串联部件想要这条路走通&#xff0c;只有…

软件测试行业7年了,薪资从10k到了22k,感觉到头了?

蜕变之前 明天的希望&#xff0c;让我们忘了今天的痛苦。 怎样区别一个废柴和一个精英&#xff1f;看外貌&#xff0c;看气质&#xff0c;看谈吐&#xff0c;看消费… 有人忙着把人和人进行分类&#xff0c;有人忙着怎么从这一阶层过渡到上一阶层。当你很累的时候&#xff0c…

引入外部文件实现步骤

1.引入数据库相关依赖 2.创建外部属性文件&#xff0c;properties格式&#xff0c;定义数据信息&#xff1a;用户名 密码 地址等 3.创建spring配置文件&#xff0c;引入context命名空间&#xff0c;引入属性文件&#xff0c;使用表达式完成注入 <beans xmlns"http://w…

交友项目【集成环信Api】

目录 1&#xff1a;自动装配 2&#xff1a;查询用户环信账户 3&#xff1a;环信ID查询用户信息 1&#xff1a;自动装配 在项目中集成环信API&#xff0c;完成即时通信等 环信官方文档地址&#xff1a;Java Server SDK [IM 开发文档] 自动装配模块&#xff1a; pom文件相关…

2.数据结构期末复习之顺序表和链表

1.表是可以是线性结构 学号姓名19(数据项)jams(数据项)20(数据项)ming(数据项) 19 jams或 20 ming是数据元表单个的是数据项‘’线性结构可以表示为 19 jams->20 ming2.什么是逻辑结构?:具有相同类型的有限序列(元素排序的位置,排兵布阵操作的方法) a1 a2 a3 .... an (空…

jenkins流水线使用入门示例

之前采用Jenkins的自由风格构建的项目&#xff0c;每个步骤流程都要通过不同的方式设置&#xff0c;并且构建过程中整体流程是不可见的&#xff0c;无法确认每个流程花费的时间&#xff0c;并且问题不方便定位问题。 Jenkins的Pipeline可以让项目的发布整体流程可视化&#xf…

低代码开发大势所趋,这款无代码开发平台你值得拥有

文章目录 什么是低代码iVX和其它低代码的平台的区别没有创新的“拼凑”&#xff0c;没有好东西iVX在线编辑器体验 什么是低代码 低代码&#xff08;Low Code&#xff09;是一种可视化的应用开发方法&#xff0c;用较少的代码、以较快的速度来交付应用程序&#xff0c;将程序员…

ElasticSearch漫游 (1.安装ELK)

前期准备&#xff1a; 请搭建好linux环境 推荐使用centos7系统请关闭linux防火墙请安装好docker 安装ES 创建网络 我们需要部署kibana容器&#xff0c;因此需要让es和kibana互联&#xff0c;这里先创建一个网络。 docker network create es-net加载es镜像 运行docker命令 部…

智能无线温振传感器:提高锂电设备故障诊断精度的利器

当今锂电工厂对于设备可靠性和生产效率的要求越来越高&#xff0c;而设备故障诊断是其中非常重要的一环。针对锂电设备的振动和温度等健康状态的监测&#xff0c;智能无线温振传感器是一款非常有用的工具。 图.太阳能面板生产&#xff08;iStock&#xff09; 智能无线温振传感器…