用C++实现一个哈希桶并封装实现 unordered_map 和 unordered_set

news2024/11/16 9:20:03

目录

哈希桶的实现

封装 unordered_map 和 unordered_set

封装代码

HashTable.h

MyUnorderedMap.h

MyUnorderedSet.h


哈希桶,又叫开散列法。开散列法又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。具体如下图例:

哈希桶的实现原理

首先,思路是用 vector 来作为基础容器,里面存储的数据类型是一个结构器结点类型,结点中存储模板类型的数据和 _next 指针。当要插入数据的时候,先根据这个数据利用除留余数法计算出它的 hashi,然后再头插到对应 vector 的位置。

一般设置哈希桶的负载因子为1,当哈希桶的负载因子恰好等于 1 的时候,就要进行扩容。哈希表的扩容必须要异地扩容并且将原哈希桶中的数据再次挨个插入到新的哈希桶中,最后将这个哈希桶的数据与原来的哈希桶交换。当然,析构函数也要自己来写,因为必须将所有结点挨个释放!

哈希表的查找。哈希表的查找效率是非常高的,几乎接近于 O(1)。先计算出要查找数据的 hashi,再根据以 hashi 为头结点的链表往下查找与之对应的数据,并返回找到位置的指针。如果找不到,就返回空指针。

哈希表的删除。 哈希表的删除也是查找的那一套思路,但需要记录一个prev指针,因为需要对prev指针和 next 指针进行链接。

封装 unordered_map 和 unordered_set

说实话这个封装,真是让人太头大了。

我就在这里介绍几个比较容易出错的点。

前置声明,可以解决相互依赖的问题,当定义在源文件靠上位置的类想使用靠下位置的类时,而编译器又只能向上,所以要在靠上位置的类前面声明一下。例如在迭代器类中想使用哈希表,就需要提前声明一下。

格式就是上图中写的:

模板参数列表

class 类名 

一个类 A 想访问另一个类 B 的私有,需要在类 B 中友元声明 类 A,且友元声明的位置可以在任意位置(public private protected),这样,类 A 中就可以访问类 B 的私有了!

代码实现

(里面一些需要注意的点都在代码中注释标注)

HashTable.h

#pragma once
#include <iostream>
#include <vector>
#include <string>
using namespace std;
namespace Hash_backet
{
	template<class T>
	struct HashNode
	{
		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
		HashNode<T>* _next;
		T _data;
	};
	template<class K>
	struct HashOfi
	{
		size_t operator()(const K& key)
		{
			return (size_t)key;
		}
	};
	template<>
	struct HashOfi<string>
	{
		size_t operator()(const string& key)
		{
			size_t hashi = 0;
			for (size_t i = 0; i < key.size(); i++)
			{
				hashi *= 31;
				hashi += key[i];
			}
			return hashi;
		}
	};
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class KeyOFT, class Hash, class Ptr, class Ref>
	class __HashIterator
	{
		template<class K, class Hash>
		friend class unordered_set;
		template<class K, class T, class Hash>
		friend class unordered_map;
		template<class K, class T, class KeyOfT, class Hash>
		friend class HashTable;
		typedef __HashIterator<K, T, KeyOFT, Hash, Ptr, Ref> self;
		typedef HashNode<T> Node;	
	public:
		__HashIterator(Node* node, HashTable<K, T, KeyOFT, Hash>* php)
			:_node(node)
			,_php(php)
		{}
		__HashIterator(Node* node, const HashTable<K, T, KeyOFT, Hash>* php)
			:_node(node)
			, _php(php)
		{}
		Hash ky;
		KeyOFT kt;
		const HashTable<K, T, KeyOFT, Hash>* _php;
		self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				size_t hashi = ky(kt(_node->_data)) % _php->_tables.size();
				++hashi;
				while (hashi < _php->_tables.size())
				{
					if (_php->_tables[hashi])
					{
						_node = _php->_tables[hashi];
						break;
					}
					hashi++;
				}
				if (hashi == _php->_tables.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}
		bool operator!=(const self &it)
		{
			return _node != it._node;
		}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
	private:
		Node* _node;
	};
 
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		template<class K, class T, class KeyOFT, class Hash, class Ptr, class Ref>
		friend class __HashIterator; // 后面要使用 __Iterator 的私有,因此要在类内友元声明
		typedef __HashIterator<K, T, KeyOfT, Hash, T*, T&> iterator;
		typedef __HashIterator<K, T, KeyOfT, Hash, const T*, const T&> const_iterator;
		KeyOfT kt;
		Hash ky;
		HashTable()
		{
			_tables.resize(10);
		}
		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);
		}
		const_iterator begin() const
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					return const_iterator(cur, this);
				}
			}
			return end();
		}
		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}
		iterator Find(const K& key)
		{
			size_t hashi = ky(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kt(cur->_data) == key)
				{
					return iterator(cur,this);
				}
				cur = cur->_next;
			}
			return iterator(nullptr, this);
		}
		pair<iterator, bool> Insert(const T& data)
		{
			if (Find(kt(data))._node)  //要使用_node 需要在前面进行友元声明
			{
				// 有元素,不允许插入
				return make_pair(Find(kt(data)),false);
			}
			if (_n == _tables.size())
			{
				// 扩容,异地扩容,直接将结点挨个弄下去
				HashTable<K, T, KeyOfT, Hash> newht;
				size_t newsize = 2 * _tables.size();
				newht._tables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					if (_tables[i]) // 顺序表元素数组结点不为空
					{
						// 将这个结点的结点全部拿下来,链接到新结点
						Node* cur = _tables[i];
						while (cur)
						{
							newht.Insert(cur->_data);
							cur = cur->_next;
						}
					}
				}
				_tables.swap(newht._tables);
			}
			// 计算 hashi
			size_t hashi = ky(kt(data)) % _tables.size();
			// 头插
			Node* newnode = new Node(data);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			_n++;
			return make_pair(iterator(_tables[hashi], this),true);
		}
		bool Erase(const K& key)
		{
			size_t hashi = ky(key) % _tables.size();
			Node* cur = _tables[hashi];
			if (cur)
			{
				Node* prev = nullptr;
				while (cur)
				{
					if (kt(cur->_data) == key)
					{
						if (cur == _tables[hashi])
						{
							_tables[hashi] = cur->_next;
						}
						else
						{
							Node* next = cur->_next;
							prev->_next = next;
						}
						delete cur;
						break;
					}
					prev = cur;
					cur = cur->_next;
				}
				return true;
			}
			else
			{
				return false;
			}
		}
		~HashTable()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				if (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
			}
		}
	private:
		vector<Node*> _tables;
		size_t _n = 0;
	};
}

MyUnorderedMap.h

#pragma once
#include "HashTable.h"
namespace Hash_backet
{ 
	template<class K, class T, class Hash = HashOfi<K>>
	class unordered_map
	{
	public:
		struct UnMapKeyOfT
		{
			const K& operator()(const pair<K, T>& kv)
			{
				return kv.first;
			}
		};
		// 通过类域去访问HashTable 里面的 iterator,编译器其实是不能区分到底是 静态成员变量 还是 内嵌类型 的
		// 前面加一个 typename,就相当于一个给编译器的声明,这是一个内嵌类型(保证编译的时候不会报错),等到实例化的时候,再找具体的类来替换
		typedef typename Hash_backet::HashTable<K, pair<const K, T>, UnMapKeyOfT, Hash>::iterator iterator; 
		typedef typename Hash_backet::HashTable<K, pair<const K, T>, UnMapKeyOfT, Hash>::const_iterator const_iterator;
		pair<iterator, bool> insert(const pair<K, T>& kv)
		{
			return _ht.Insert(kv);
		}
		T& operator[](const K& key)
		{
			return (_ht.insert(make_pair(key, T()))).first->second; // -> 可以得到 _data 的引用,相当于 first._node.second
		}
		const T& operator[](const K& key) const
		{
			return (_ht.insert(make_pair(key, T()))).first->second; // -> 可以得到 _data 的引用,相当于 first._node.second
		}
		iterator find(const K& key)
		{
			return _ht.Find(key);
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		const_iterator begin() const // const 放后面,表明调用这个成员函数的对象是用 const 来修饰的
		{
			return _ht.begin();
		}
		const_iterator end() const  // const 放后面,表明调用这个成员函数的对象是用 const 来修饰的
		{
			return _ht.end();
		}
	private:
		HashTable<K, pair<const K, T>, UnMapKeyOfT, Hash> _ht;
	};
}

MyUnorderedSet.h

#pragma once
#include "HashTable.h"
namespace Hash_backet
{
	template<class K, class Hash = HashOfi<K>>
	class unordered_set
	{
	public:
		struct UnSetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		// 通过类域去访问HashTable 里面的 iterator,编译器其实是不能区分到底是 静态成员变量 还是 内嵌类型 的
		// 前面加一个 typename,就相当于一个给编译器的声明,这是一个内嵌类型(保证编译的时候不会报错),等到实例化的时候,再找具体的类来替换
		typedef typename Hash_backet::HashTable<K, K, UnSetKeyOfT, Hash>::const_iterator iterator;
		typedef typename Hash_backet::HashTable<K, K, UnSetKeyOfT, Hash>::const_iterator const_iterator;
		pair<iterator, bool> insert(const K& key)
		{
			auto ret = _ht.Insert(key);
			return pair<const_iterator, bool>(const_iterator(ret.first._node, ret.first._php), ret.second);
		}
		iterator find(const K& key)
		{
			auto ret = _ht.Find(key);
			return iterator(ret._node, ret._php);
		}
		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
		const_iterator begin() const // 无论是 iterator 还是 const_iterator 都调用 HashTable 中 const 类型的end() 和 begin()
		{
			return _ht.begin();
		}
		const_iterator end() const
		{
			return _ht.end();
		}
	private:
		HashTable<K, K, UnSetKeyOfT, Hash> _ht;
	};
}

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

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

相关文章

spring问题点

1.事务 1.1.事务传播 同一个类中 事务A调非事务B B抛异常 AB事务生效&#xff08;具有传播性&#xff09; 同一个类中 事务A调非事务B A抛异常 AB事务生效 也就是主方法加了事务注解 则方法内调用的其他本类方法无需加事务注解&#xff0c; 发生异常时可以保证事务的回滚 最常…

安科瑞消防设备电源监控系统在地铁工程的设计与应用

【摘要】&#xff1a;本文介绍了地铁工程中消防设备电源监控系统设置的必要性及规范求&#xff0c;分析了监控设计方案&#xff0c;提出该系统在地铁工程中的应用要求及建议&#xff0c;以供地铁工程建设参考。消防设备电源监控系统主要针对消防用电设备的电源进行实时的监控&a…

在 Elastic Agent 中为 Logstash 输出配置 SSL/TLS

要将数据从 Elastic Agent 安全地发送到 Logstash&#xff0c;你需要配置传输层安全性 (TLS)。 使用 TLS 可确保你的 Elastic Agent 将加密数据发送到受信任的 Logstash 服务器&#xff0c;并且你的 Logstash 服务器从受信任的 Elastic Agent 客户端接收数据。 先决条件 确保你…

深入理解指针(3)

⽬录 1. 字符指针变量 2. 数组指针变量 3. ⼆维数组传参的本质 4. 函数指针变量 5. 函数指针数组 6. 转移表 1. 字符指针变量 在指针的类型中我们知道有⼀种指针类型为字符指针 char* ; ⼀般使⽤: int main() {char ch w;char *pc &ch;*pc w;return 0; } 还有…

面试150 二进制求和 位运算

Problem: 67. 二进制求和 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( n ) O(n) O(n) Code class Solution {public String addBinary(String a, String b){StringBuilder ans new Stri…

某赛通电子文档安全管理系统 UploadFileToCatalog SQL注入漏洞复现

0x01 产品简介 某赛通电子文档安全管理系统(简称:CDG)是一款电子文档安全加密软件,该系统利用驱动层透明加密技术,通过对电子文档的加密保护,防止内部员工泄密和外部人员非法窃取企业核心重要数据资产,对电子文档进行全生命周期防护,系统具有透明加密、主动加密、智能…

【类和对象】4

日期类的拓展 c语言中的printf函数只能打印内置类型&#xff0c;为了弥补这一不足&#xff0c;c利用运算符重载可以打印自定义类型。 void operator<<(ostream&out);//声明在date.h中void Date::operator<<(ostream& out)//定义在date.cpp中 {out<<…

C++学习Day01之namespace命名空间

目录 一、程序及输出1.1 命名空间用途&#xff1a; 解决名称冲突1.2 命名空间内容1.3 命名空间必须要声明在全局作用域下1.4 命名空间可以嵌套命名空间1.5 命名空间开放&#xff0c;可以随时给命名空间添加新的成员1.6 命名空间可以是匿名的1.7 命名空间可以起别名 二、分析与总…

常见关系型数据库产品介绍

更新晚了&#xff0c;不好意思啦&#xff01;继关系型数据库的介绍与历史今天主要和大家分享关系型数据库有哪些产品以及简单的背景介绍。这篇文章介意宝宝们听着舒缓的音乐静静享受。 关系型数据库的产品有很多&#xff0c;下面和大家分享一些比较有名的、使用比较广泛的关系…

了解野指针与assert断言 拿捏指针的使用!

目录 1.野指针 野指针的成因&#xff1a; 2.规避野指针 3.assert断言 创作不易&#xff0c;宝子们&#xff01;如果这篇文章对你们有帮助的话&#xff0c;别忘了给个免费的赞哟~ 1.野指针 概念&#xff1a;野指针就是指针指向的位置是不可知的&#xff08;随机的、不正确的…

AspNet web api 和mvc 过滤器差异

最近在维护老项目。定义个拦截器记录接口日志。但是发现不生效 最后发现因为继承的 ApiController不是Controller 只能用 System.Web.Http下的拦截器生效。所以现在总结归纳一下 Web Api: System.Web.Http.Filters.ActionFilterAttribute 继承该类 Mvc: System.Web.Mvc.Ac…

《苍穹外卖》电商实战项目实操笔记系列(P123~P184)【下】

史上最完整的《苍穹外卖》项目实操笔记系列【下篇】&#xff0c;跟视频的每一P对应&#xff0c;全系列10万字&#xff0c;涵盖详细步骤与问题的解决方案。如果你操作到某一步卡壳&#xff0c;参考这篇&#xff0c;相信会带给你极大启发。 上篇&#xff1a;P1~P65《苍穹外卖》项…

【Python机器学习系列】建立多层感知机模型预测心脏疾病(完整实现过程)

一、引言 前文回顾&#xff1a; 【Python机器学习系列】建立决策树模型预测心脏疾病&#xff08;完整实现过程&#xff09; 【Python机器学习系列】建立支持向量机模型预测心脏疾病&#xff08;完整实现过程&#xff09; 【Python机器学习系列】建立逻辑回归模型预测心脏疾…

Java 基于 SpringBoot+Vue 的考研论坛管理系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Transformer实战-系列教程2:Transformer算法解读2

&#x1f6a9;&#x1f6a9;&#x1f6a9;Transformer实战-系列教程总目录 有任何问题欢迎在下面留言 Transformer实战-系列教程1&#xff1a;Transformer算法解读1 Transformer实战-系列教程2&#xff1a;Transformer算法解读2 5、Multi-head机制 在4中我们的输入是X&#x…

【内置对象·js】

数学对象 document.write("圆周率为 " Math.PI "<br>");日期对象 var date new Date(); // 实例化 Date 对象var month date.getMonth() 1; // 获取月份&#xff0c;取值为 0&#xff08;一月&#xff09;到 11&#xff08;十二月&#xff09;之…

蓝桥杯第九届省赛题-----彩灯控制系统笔记

题目要求&#xff1a; 一、 基本要求 1.1 使用 CT107D 单片机竞赛板&#xff0c;完成“彩灯控制器”功能的程序设计与调 试&#xff1b; 1.2 设计与调试过程中&#xff0c;可参考组委会提供的“资源数据包”&#xff1b; 1.3 Keil 工程文件以准考证号命名&#xff0c…

百无聊赖之JavaEE从入门到放弃(十八)其他常用类

目录 一.Math 类 二.Random 类 三.File类 四.枚举 一.Math 类 java.lang.Math 提供了一系列静态方法用于科学计算&#xff1b;常用方法如下&#xff1a; abs 绝对值 acos,asin,atan,cos,sin,tan 三角函数 sqrt 平方根 pow(double a, double b) a 的 b 次幂 max(double a,…

24年2月深度学习

参考&#xff1a; RAPTOR: RECURSIVE ABSTRACTIVE PROCESSING FOR TREE-ORGANIZED RETRIEVAL 树结构检索方案。

Linux文件系统和磁盘的 I/O 常用性能工具指标详解

I/O 栈的全景图&#xff1a; 把 Linux 存储系统的 I/O 栈&#xff0c;由上到下分为三个层次&#xff0c;分别是文件系统层、通用块层和设备层。 文件系统层&#xff0c;包括虚拟文件系统和其他各种文件系统的具体实现。它为上层的应用程序&#xff0c;提供标准的文件访问接口&…