【C++】哈希实现unordered_map/set

news2024/11/26 9:55:40

关于哈希模拟实现unordered_map/set,与红黑树模拟实现map/set的大体思路相似。

【C++】红黑树模拟实现map和set-CSDN博客


HashTable的迭代器

operator++

template<class K,class T,class KeyOfT>
struct __HashTableIterator
{
	typedef __HashTableIterator<K, T, KeyOfT> Self;
	typedef HashNode<T> Node;
	Node* _node;
	__HashTableIterator(const T& node)
		:_node(node)
	{		}
    Self operator++()
    {
        //...
    }
}

如果我们不管插入元素的顺序,只要求遍历,如何实现operator++呢?

当前桶遍历完了,继续找下一个桶遍历。计算当前节点在哪个桶,再往后找不为空的桶。所以迭代器里不能只有节点还要传一个HashTable。

struct HashNode
{
	T _data;
	HashNode<T>* _next;
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	{		}
};
//...
template<class K,class T,class KeyOfT,class Hash>
struct __HashTableIterator
{
	typedef __HashTableIterator<K, T, KeyOfT,Hash> Self;
	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, Hash> HT;
	Node* _node;
	HT* _pht;
	__HashTableIterator(const T& node,HT* pht)
		:_node(node)
		,_pht(pht)
	{		}
	Self operator++()
	{
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			KeyOfT koft;
			//当前桶遍历完了,找下一个桶继续遍历
			size_t i = _pht->HashFunc(koft(_node->_data)) % _pht->tablse.size();
			++i;
			for (; i < _pht.size(); ++i)
			{
				Node* cur = _pht->tables[i];
				if (cur)
				{
					_node = cur;
					return *this;
				}
			}
			_node = nullptr;
		}
		return *this;
	}
};
//...
template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
	typedef HashNode<T> Node;
public:
    typedef __HashTableIterator<K, T, KeyOfT, Hash> iterator;
	iterator begin()
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			if (_tables[i])
			{
				return iterator(_tables[i],this);
			}
		}
		return end();
	}
	iterator end()
	{
		return iterator(nullptr,this);
	}
    //...
private:
	vector<Node*> _tables;
	size_t _num = 0;
}

发现会报错,找不到__HashIterator里找不到HashTable,原因是,HashTable是在__HashIterator的下面定义的,编译器找不到,能找到HashNode是因为HashNode在__HashIterator上面已经定义过了。

那把__HashIterator定义在HashTable后面呢?也不行,HashTable中也用到了__HashIterator。

所以要在__HashIterator加上HashTable的声明。

前置声明

//前置声明
template<class K, class T, class KeyOfT, class Hash>
class HashTable;

template<class K,class T,class KeyOfT,class Hash>
struct __HashTableIterator
{ }

 unordered_map/set是单向迭代器,没有operator--。

注意:以上实现的operator++方式,没有按照插入顺序去遍历,跟STL库的实现方式不同。

MyUnorderedSet.hpp

#include"HashTable.hpp"
using namespace OPEN_HASH;
namespace zxa
{
	template<class K,class Hash= _Hash<K>>
	class unordered_set
	{
		struct SetKOfT
		{
			const K& operator()(const K& k)
			{
				return k;
			}
		};
	public:
		typedef typename HashTable<K, K, SetKOfT, Hash>::iterator iterator;
		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		pair<iterator,bool> Insert(const K& k)
		{
			return _ht.Insert(k);
		}
	private:
		HashTable<K, K, SetKOfT,Hash> _ht;
	};
	void test_unordered_set();
}

 MyUnorderedMap.hpp

#include"HashTable.hpp"
using namespace OPEN_HASH;

namespace zxa
{
	template<class K,class V,class Hash= _Hash<K>>
	class unordered_map
	{
		struct MapKOfT
		{
			const K& operator()(const pair<K,V> & kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename HashTable<K, pair<K, V>, MapKOfT, Hash>::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);
		}
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		HashTable<K, pair<K, V>, MapKOfT,Hash> _ht;
	};
	void test_unordered_map();
}

 HashTable.hpp

#pragma once
#include<vector>
#include<string>
#include<iostream>
using namespace std;

namespace OPEN_HASH
{
	template<class T>
	struct HashNode
	{
		T _data;
		HashNode<T>* _next;
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{		}
	};

	//前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct __HashTableIterator
	{
		typedef __HashTableIterator<K, T,Ref,Ptr, KeyOfT,Hash> Self;
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		Node* _node;
		HT* _pht;
		__HashTableIterator(Node* node,HT* pht)
			:_node(node)
			,_pht(pht)
		{		}
		Ref operator*()
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT koft;
				//当前桶遍历完了,找下一个桶继续遍历
				size_t i = _pht->HashFunc(koft(_node->_data)) % _pht->_tables.size();
				++i;
				for (; i < _pht->_tables.size(); ++i)
				{
					Node* cur = _pht->_tables[i];
					if (cur)
					{
						_node = cur;
						return *this;
					}
				}
				_node = nullptr;
			}
			return *this;
		}
		bool operator!=(const Self& s)
		{
			return _node != s._node;
		}
		bool operator==(const Self& s)
		{
			return _node == s._node;
		}
	};

	template<class K>
	struct _Hash
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
	//模板特化 
	template<>
	struct _Hash<string>
	{
		size_t operator()(const string& key)
		{
			size_t hash = 0;
			for (size_t i = 0; i < key.size(); ++i)
			{
				hash *= 131;//也可以乘以31、131、1313、13131、131313..  
				hash += key[i];
			}
			return hash;
		}
	};

	template<class K, class T,class KeyOfT, class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;
	public:
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct __HashTableIterator;
		
		typedef __HashTableIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef __HashTableIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;

		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return iterator(_tables[i],this);
				}
			}
			return end();
		}
		iterator end()
		{
			return iterator(nullptr,this);
		}
		const_iterator begin() const
		{
			for (size_t i = 0; i < _tables.size(); ++i)
			{
				if (_tables[i])
				{
					return const_iterator(_tables[i], this);
				}
			}
			return end();
		}
		const_iterator end() const
		{
			return const_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 HashFunc(const K& key)
		{
			Hash hash;
			return hash(key);
		};

		size_t GetNextPrime(size_t num)
		{
			const int PRIMECOUNT = 28;
			static const size_t primeList[PRIMECOUNT] =
			{
				53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
				49157ul, 98317ul, 196613ul, 393241ul, 786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,
				25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,805306457ul,
				1610612741ul, 3221225473ul, 4294967291ul
			};
			size_t i = 0;
			for (; i < PRIMECOUNT; ++i)
			{
				if (primeList[i] > num)
					return primeList[i];
			}
			return primeList[i];
		}

		pair<iterator,bool> Insert(const T& data)
		{
			KeyOfT koft;
			//如果负载因子等于1,增容
			// 1、开2倍大小的新表
			// 2、把旧表中的数据重新映射到新表中
			// 3、释放旧表
			if (_tables.size() == _num)
			{
				//size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				size_t newsize = GetNextPrime(_tables.size());
				vector<Node*> newtables;
				newtables.resize(newsize);
				for (size_t i = 0; i < _tables.size(); ++i)
				{
					Node* cur = _tables[i];
					//将旧表中的节点取下来重新计算在新表中的位置
					while (cur)
					{
						Node* next = cur->_next;
						size_t index = HashFunc(koft(cur->_data)) % newtables.size();
						cur->_next = newtables[index];
						newtables[index] = cur;
						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newtables);
			}
			//计算元素在表中的位置
			size_t index = HashFunc(koft(data)) % _tables.size();
			Node* cur = _tables[index];
			//1、先查找在不在表中
			while (cur)
			{
				if (koft(cur->_data) == koft(data))
				{
					return make_pair(iterator(cur, this), false);
				}
				else
				{
					cur = cur->_next;
				}
			}
			//2、头插到挂的链表中(也可以尾插)
			Node* newnode = new Node(data);
			newnode->_next = _tables[index];
			_tables[index] = newnode;
			++_num;
			return make_pair(iterator(newnode,this),true);
		}
		pair<iterator,bool> Find(const T& key)
		{
			KeyOfT koft;
			size_t index = HashFunc(key) % _tables.size();
			Node* cur = _tables[index];
			while (cur)
			{
				if (koft(cur->_data) == key)
				{
					return make_pair(iterator(cur, this), true);
				}
			}
			return make_pair(iterator(nullptr, this), false);
		}
		bool Erase(const T& key)
		{
			KeyOfT koft;
			size_t index = HashFunc(key) % _tables.size();
			Node* cur = _tables[index];
			Node* prev = nullptr;
			while (cur)
			{
				if (koft(cur->_data) == key)
				{
					//prev为空,要删除的为头节点
					if (prev == nullptr)
					{
						_tables[index] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _num = 0;
	};
}

Test.cpp

#define _CRT_SECURE_NO_WARNINGS 
#include"MyUnorderedMap.hpp"
#include"MyUnorderedSet.hpp"
#include"HashTable.hpp"

void zxa::test_unordered_set()
{
	unordered_set<int> s;
	s.Insert(1);
	s.Insert(3);
	s.Insert(15);
	s.Insert(22);
	s.Insert(40);
	unordered_set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << endl;
		++it;
	}
	cout << endl;
}
void zxa::test_unordered_map()
{
	unordered_map<string, string> dict;
	dict.Insert(make_pair("sort", "排序"));
	dict.Insert(make_pair("left", "右边"));
	dict.Insert(make_pair("string", "字符串"));
	dict["left"] = "左边";
	dict["right"] = "右边";
	unordered_map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	cout << endl;
}
int main()
{
	zxa::test_unordered_set();
	zxa::test_unordered_map();
	return 0;
}

运行结果

补充

typename关键字

当在模板定义中引用一个依赖于模板参数的类型成员时,必须使用typename来明确告诉编译器该名称是一个类型。这是因为模板参数在编译时是未知的,编译器无法确定一个特定的成员名称是否是一个类型还是一个静态成员变量、枚举值或其他非类型成员。

typedef typename HashTable<K, K, SetKOfT, Hash>::iterator iterator;

typename关键字告诉编译器,HashTable<K, K, SetKOfT, Hash>::iterator 是一个类型。由于HashTable是一个模板类,模板参数在编译时是未知的,而模板类的成员iterator在解析时不是已知的类型,因此使用typename来明确指出iterator是一个类型名称。

优化

当表的大小是一个素数时,发生冲突的概率会降低一些。 

//计算元素在表中的位置
size_t index = HashFunc(koft(data)) % _tables.size();
//新表的空间
size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;

除留余数法,最好模一个素数,如何每次快速取一个类似两倍关系的素数?

使用以下方法(STL库中使用的也是这个):

size_t GetNextPrime(size_t num)
{
	const int PRIMECOUNT = 28;
	static const size_t primeList[PRIMECOUNT] =
	{
		53ul, 97ul, 193ul, 389ul, 769ul,1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
		49157ul, 98317ul, 196613ul, 393241ul, 786433ul,1572869ul, 3145739ul, 6291469ul, 12582917ul,
		25165843ul,50331653ul, 100663319ul, 201326611ul, 402653189ul,805306457ul,
		1610612741ul, 3221225473ul, 4294967291ul
	};
	size_t i = 0;
	for (; i < PRIMECOUNT; ++i)
	{
		if (primeList[i] > num)
			return primeList[i];
	}
	return primeList[i];
}

本文主要实现了HashTable迭代器的operator++,其他细节和红黑树模拟map/se方式类似,这里直接实现了。

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

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

相关文章

电梯导航 - 点击标题跳转对应区域

需求 点击标题&#xff0c;使用a标签的锚点自动跳到对应区域滚动区域&#xff0c;右边自动切换对应的标题 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"wid…

大话红黑树之(3)进阶解析

红黑树高阶知识讲解 红黑树作为一种自平衡的二叉查找树&#xff08;BST&#xff09;&#xff0c;在大多数语言和库中有着广泛应用。它能够在常规操作&#xff08;查找、插入、删除等&#xff09;中保持 O(log n) 的时间复杂度。这篇文章从红黑树的高级特性、性能优化、旋转机制…

U9的插件开发之BE插件(1)

U9插件可分为&#xff1a;BE插件、BP插件、UI插件&#xff1b; BE(Business Entity) 简单就是指实体&#xff0c;U9的元数据。 我的案例是设置BE默认值&#xff0c;即在单据新增时&#xff0c;设置单据某一个字段的默认值&#xff0c;具体如下&#xff1a; 1.插件开发工具&a…

使用virtualenv导入ssl模块找不到指定的模块

最近在学习tensorflow&#xff0c;由于教程里面使用的是virtualenv&#xff0c;所以就按照教程开始安装了虚拟环境。但是在使用的时候&#xff0c;卡在了import ssl这一步&#xff0c;提示如下错误 >>> import ssl Traceback (most recent call last):File "<…

word删除空白页 | 亲测有效

想要删掉word里面的末尾空白页&#xff0c;但是按了delete之后也没有用 找了很久找到了以下亲测有效的方法 1. 通过鼠标右键在要删除的空白页面处显示段落标记 2. 在字号输入01&#xff0c;按ENTER&#xff08;回车键&#xff09; 3.成功删除了&#xff01;&#xff01; PS…

python excel如何转成json,并且如何解决excel转成json时中文汉字乱码的问题

1.解决excel转成json时中文汉字乱码的问题 真的好久没有打开这个博客也好久没有想起来记录一下问题了&#xff0c;今天将表格测试集转成json格式的时候遇到了汉字都变成了乱码的问题&#xff0c;虽然这不是个大问题&#xff0c;但是编码问题挺烦人的&#xff0c;乱码之后像下图…

018集——c# 实现CAD添加侧栏菜单(WPF控件)(CAD—C#二次开发入门)

本例实现的效果如下&#xff1a; 第一步&#xff1a;添加引用 using UserControl System.Windows.Controls.UserControl; using System.Windows.Forms.Integration;//PaletteSet integration 第二步 <UserControl x:Class"AcTools.UserControl1"xmlns"htt…

Pytorch学习--如何下载及使用Pytorch中自带数据集,如何把数据集和transforms联合在一起使用

一、标准数据集使用 pytorch官网–标准数据集 这里以CIFAR10数据集为例&#xff1a;CIFAR10 下载数据集 代码&#xff1a; import torchvision train_datatorchvision.datasets.CIFAR10(root"datasets",trainTrue,downloadTrue) test_datatorchvision.datasets.…

运维加薪之Ansible(DevOps Salary Increase with Ansible。‌)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…

微服务架构学习笔记

#1024程序员节|征文# 微服务架构作为现代软件开发中的热门技术架构&#xff0c;因其灵活性和可扩展性&#xff0c;逐渐成为许多企业系统设计的首选。以下是关于微服务的一些学习笔记&#xff0c;涵盖微服务的核心概念、优缺点、设计原则以及常用工具等方面。 1. 微服务是什么&…

【Docker】docker | 部署nginx

一、概述 记录下nginx的部署流程&#xff1b;将conf配置文件映射到宿主机 前提依赖&#xff1a;自行准备nginx的镜像包 二、步骤 1、运行、无映射 docker run --name nginx -p 80:80 -d nginx:1.18.0-alpine 80&#xff1a;80&#xff0c;前面是宿主机端口&#xff1b;如果冲…

Spring Boot植物健康系统:智慧农业的新趋势

6系统测试 6.1概念和意义 测试的定义&#xff1a;程序测试是为了发现错误而执行程序的过程。测试(Testing)的任务与目的可以描述为&#xff1a; 目的&#xff1a;发现程序的错误&#xff1b; 任务&#xff1a;通过在计算机上执行程序&#xff0c;暴露程序中潜在的错误。 另一个…

ripro-v5-8.3开心版主题源码

1、下载主题源码ripro-v5.zip进行安装。 2、下载激活文件ripro-v5-active.php上传到wp根目录&#xff0c;访问一次&#xff0c;即可激活。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89915698 更多资源下载&#xff1a;关注我。

队列(数据结构)——C语言

目录 1.概念与结构 2.队列的实现 初始化QueueInit 申请新节点BuyNode 入队QueuePush 判断队为空QueueEmpty 出队QueuePop 读取队头数据QueueFront 读取队尾数据QueueBack 元素个数QueueSize 销毁队列QueueDestroy 3.整体代码 (文章中结点和节点是同一个意思) 1.概…

闯关leetcode——203. Remove Linked List Elements

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/remove-linked-list-elements/description/ 内容 Given the head of a linked list and an integer val, remove all the nodes of the linked list that has Node.val val, and return the new …

C语言实现二叉树和堆

1.二叉树概念及结构 1.1概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合: 1. 或者为空 2. 由一个根结点加上两棵别称为左子树和右子树的二叉树组成 从上图可以看出&#xff1a; 1. 二叉树不存在度大于2的结点 2. 二叉树的子树有左右之分&#xff0c;次序不能颠倒&…

案例分析-系统开发基础

案例分析考点分类&#xff1a; 软件架构设计&#xff1a;考质量属性、软件架构分析(第一题)、软件架构评估、MVC架构、SOA架构、ESB、J2EE架构、DSSA、ABSD等(第二题)、系统开发基础&#xff1a;考UML的图、关系的识别&#xff0c;尤其是类图、用例图、活动图、状态图、设计模式…

Flutter 状态管理框架Get

状态管理框架 Get的使用 目录 状态管理框架 Get的使用 GetMaterialApp 路由的注册 路由的跳转 middlewares的使用 组件使用 defaultDialog bottomSheet snackbar 状态刷新有很多种方式 ValueBuilder Obx 基础使用 是时候引入GetxController, 也是Get里面的常用的 G…

DevOps实践:在GitLab CI/CD中集成静态分析Helix QAC的工作原理与优势

基于云的GitLab CI/CD平台使开发团队能够简化其CI/CD流程&#xff0c;并加速软件开发生命周期&#xff08;SDLC&#xff09;。 将严格的、基于合规性的静态分析&#xff08;如Helix QAC所提供&#xff09;作为新阶段添加到现有的GitLab CI/CD流程中&#xff0c;将进一步增强SD…

华为云购买弹性云服务器(教程)

配置弹性云服务器 基础配置 实例 操作系统