C++进阶--使用哈希表实现unordered_map和unordered_set的原理与实例

news2024/11/18 21:24:40

本文将介绍如何使用哈希表来实现C++ STL库中的unordered_map和unordered_set容器。我们将会解释哈希表的基本原理,并给出具体的代码示例,帮助读者更好地理解和应用哈希表。

哈希原理讲解–链接入口

set和map的实现的文章,与unordered_map实现类似

对HashTable的改造

#pragma once

template<class K>
struct Hashfunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
//特化
template<>
struct Hashfunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto a : s)
		{
			hash += a;
			hash *= 31;
		}
		return hash;
	}
};



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

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

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

	template<class K, class T, class KeyOfT, class Hash>
	struct _HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef _HTIterator<K, T, KeyOfT, Hash> Self;

		Node* _node;
		HT* _ht;

		_HTIterator(Node* node,HT* ht)
			:_node(node),
			_ht(ht)
		{}

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

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

		Self& operator++()
		{
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				KeyOfT kot;
				Hash hs;
				size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();

				hashi++;
				//循环找出存在的哈希桶位置;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}

					hashi++;
				}

				if (hashi == _ht->_tables.size())
				{
					_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,class T,class KeyOfT, class Hash>
	class HashTable
	{
		template<class K,class T,class KeyOfT,class Hash>
		friend struct _HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef _HTIterator<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);
		}
		
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}

		~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;
			}
		}

		iterator Find(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur,this);
				}

				cur=cur->_next;
			}

			return iterator(nullptr,this);
		}

 		pair<iterator,bool> Insert(const T& data)
		{
			KeyOfT kot;
			Hash hs;
			//查找是否有相同元素
			iterator it = Find(kot(data));
			if (it != end())
				return make_pair(it,false);

			//扩容
			if (_n == _tables.size())
			{
				vector<Node*> newTables(_tables.size() * 2, nullptr);
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hs(kot(cur->_data)) % newTables.size();
						cur->_next = newTables[hashi];
						newTables[hashi] = cur;

						cur = next;
					}
					_tables[i] = nullptr;
				}
				_tables.swap(newTables);
			}	

			size_t hashi = hs(kot(data)) % _tables.size();
			Node* newnode = new Node(data);
			it = iterator(newnode, this);
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;

			++_n;
			return make_pair(it,true);
		}

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data)==key)
				{
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}
					delete cur;
					_n--;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	private:
		vector<Node*> _tables;
		size_t _n;
	};
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对unordered_map和unordered_set的实现

unordered_set是一种集合存储的容器,它存储唯一的元素。我们同样可以使用哈希表来实现unordered_set,将元素映射到数组的索引。

  • 插入操作:通过哈希函数计算元素的索引,如果索引位置为空,则直接插入;否则,使用开放地址法寻找下一个可用的槽进行插入。
  • 查找操作:通过哈希函数计算元素的索引,然后比较该索引位置的元素是否与目标元素相等,如相等则返回存在;如不相等,则使用开放地址法依次查找下一个槽,直到找到相等的元素或者遇到空槽为止。
  • 删除操作:通过哈希函数计算元素的索引,然后通过循环找到对应的元素,如果这个元素有前一个结点,那么就让前一个结点链上当前结点的下一个结点,然后删除当前结点;如果当前元素没有前一个结点,那么就表示删除该节点后,哈希桶的头结点将为空;
namespace fnc
{
	template<class K,class Hash=Hashfunc<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

	public:
		typedef typename hash_bucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;

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

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

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

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

		bool Erase(const K& k)
		{
			return _ht.Erase(k);
		}


	private:
		hash_bucket::HashTable<K, const K, SetKeyOfT, Hash> _ht;
		
	};
}

unordered_map是一种键值对存储的容器,它允许通过键快速查找对应的值。我们可以使用哈希表来实现unordered_map,将键映射到数组的索引,值存储在对应的槽中。

  • 插入操作:通过哈希函数计算键的索引,如果索引位置为空,则直接插入;否则,使用开放地址法寻找下一个可用的槽进行插入。
  • 查找操作:通过哈希函数计算键的索引,然后比较该索引位置的键是否与目标键相等,如相等则返回对应的值;如不相等,则使用开放地址法依次查找下一个槽,直到找到相等的键或者遇到空槽为止。
  • 删除操作:通过哈希函数计算元素的索引,然后通过循环找到对应的元素,如果这个元素有前一个结点,那么就让前一个结点链上当前结点的下一个结点,然后删除当前结点;如果当前元素没有前一个结点,那么就表示删除该节点后,哈希桶的头结点将为空;
namespace fnc
{
	template<class K, class V, class Hash = Hashfunc<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};

	public:
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, 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);
		}

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

		bool Erase(const K& k)
		{
			return _ht.Erase(k);
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}
	private:
		hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
	};
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试

insert

在这里插入图片描述
在这里插入图片描述

find和erase

//find和erase
	void test_set2()
	{
		vector<int> v = { 3,1,5,15,45,7 };
		unordered_set<int> us;
		us.insert(3);
		us.insert(1);
		us.insert(5);
		us.insert(15);
		us.insert(45);
		us.insert(7);

		unordered_set<int>::iterator it = us.begin();
		while (it != us.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		if (us.find(15) != us.end())
		{
			cout << "15:在" << endl;
		}
		else
		{
			cout << "15:不在" << endl;
		}
		if (us.find(16) != us.end())
		{
			cout << "16:在" << endl;
		}
		else
		{
			cout<<"16:不在" << endl;
		}
		cout << endl;
		us.Erase(5);
		us.Erase(7);
		it = us.begin();
		while (it != us.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
}
void test_map2()
	{
		unordered_map<string, string> dict;
		dict.insert(make_pair("sort", "排序"));
		dict.insert(make_pair("left", "左"));
		dict.insert(make_pair("right", "右"));

		for (auto& kv : dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;
		fnc::unordered_map<string, string>::iterator it = dict.find("sort");
		if (it!=dict.end())
		{
			cout <<"找到了:" << (*it).first << ":" << (*it).second << endl;
		}
		cout << endl;
		dict.Erase("left");
		for (auto& kv : dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}

	}

在这里插入图片描述

operator[]

void test_map3()
	{
		vector<string> v = { "香蕉","苹果","橙子","西瓜","香蕉",
			"苹果","橙子","西瓜","香蕉","苹果","橙子" ,"香蕉","苹果" };
		unordered_map<string, int> dict;
		for (auto a : v)
		{
			dict[a]++;
		}

		for (auto a : dict)
		{
			cout << a.first << ":" << a.second << endl;
		}
	}

在这里插入图片描述

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

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

相关文章

ros time 时间戳改为机器开机时间

文章目录 一、问题描述二、修改方法补充1. 时间类型2. 时间数据使用方法 一、问题描述 因项目需要,需要"ros::Time::now()" 改成获取机器开机时间,此处针对rospy的机器时间修改。 二、修改方法 修改ros源码的文件 /opt/ros/noetic/lib/python3/dist-packages/ros…

是德科技keysight DSOX3104A示波器

181/2461/8938产品概述&#xff1a; Keysight(原Agilent) InfiniiVision DSOX3104A 的价位较低&#xff0c;能够在满足您苛刻预算要求的情况下提供卓越性能&#xff0c;以及可选功能。是德(原安捷伦)突破性技术可以在相同的预算条件下提供更多更出色的示波器功能 Keysight(原A…

Navicat 干货 | 通过检查约束确保 PostgreSQL 的数据完整性

数据完整性对于任何数据库系统来说都是很重要的一方面&#xff0c;它确保存储的数据保持准确、一致且有意义的。在 PostgreSQL 中&#xff0c;维护数据完整性的一个强大工具是使用检查约束。这些约束允许你定义数据必须遵守的规则&#xff0c;以防止无效数据的插入或修改。本文…

[实战]Springboot与GB28181摄像头对接。摄像头注册上线(一)

与支持国标摄像头对接 前言&#xff1a;不想看教程&#xff1f;1、准备阶段1.1、我们会学到什么&#xff1f;1.2、创建项目1.3、pom中用到的依赖1.4 打开摄像头的网址(了解配置方式) 2、代码编写2.1、增加项目配置2.2、在config目录下创建SipConfig2.3、在service目录下创建Sip…

手机录屏在哪里找?手把手教你找到录屏功能

随着智能手机的普及和移动应用的快速发展&#xff0c;手机录屏功能变得越来越受欢迎。无论是记录游戏精彩瞬间、制作教程视频&#xff0c;还是为了保存重要的视频通话内容&#xff0c;手机录屏都能帮上大忙&#xff0c;可是很多人不知道手机录屏在哪里找&#xff0c;本文将介绍…

防静电检测设备如何完善PCBA车间的防静电管控?

在PCBA&#xff08;Printed Circuit Board Assembly&#xff09;车间中&#xff0c;静电是一个极其重要的问题&#xff0c;因为静电可能对电子元器件和PCB板造成损坏&#xff0c;进而影响整个生产流程和产品质量。为了有效防止静电问题&#xff0c;企业通常会引入防静电检测设备…

US-T65 DM蓝牙5.2双模热插拔PCB

键盘使用说明索引&#xff08;均为出厂默认值&#xff09; 一些常见问题解答&#xff08;FAQ&#xff09;注意首次使用步骤蓝牙配对&#xff08;重要&#xff09;蓝牙和USB切换键盘默认层默认触发层0的FN键配置的功能默认功能层1配置的功能默认的快捷键 蓝牙参数蓝牙MAC地址管理…

Win11电脑cpu温度过高怎么办呢

Win11电脑cpu温度过高怎么办呢?有时候我们感觉电脑发烫,担心电脑过烫会不会损坏。正常情况下,cpu的温度在45~65度之间,但不排除电脑同时开了太多软件,或者在玩吃鸡、英雄联盟等的大型游戏而导致温度超过85度。只要最高温不超过85度都仍在正常的波动范围。但是超过了,电脑…

C# winform校验文件版本差异及版本号

界面 代码 using System.Diagnostics;namespace VersionTool {public partial class Form1 : Form{List<string> fileNmaes new List<string>() { "PhotoMes.Base.dll", "PhotoMes.App.exe", "PhotoMes.Cameras.dll" };public F…

零基础学习挖掘PHP网站漏洞

教程介绍 本套课程&#xff0c;分为三个阶段&#xff1a;第一阶段&#xff1a;基础篇 学习PHP开发的基础知识&#xff0c;对PHP常见的漏洞进行分析&#xff0c;第二阶段&#xff1a;进阶篇 实战PHP漏洞靶场&#xff0c;了解市面上的PHP主流网站开发技术&#xff0c;并对市面上…

【Java程序设计】【C00377】基于(JavaWeb)Springboot的社区医疗服务系统(有论文)

【C00377】基于&#xff08;JavaWeb&#xff09;Springboot的社区医疗服务系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#x…

HDFSRPC通信框架详解

本文主要对HDFSRPC通信框架解析。包括listener&#xff0c;reader&#xff0c;handler&#xff0c;responser等实现类的源码分析。注意hadoop版本为3.1.1。 写在前面 rpc肯定依赖于socket通信&#xff0c;并且使用的是java NIO。读者最好对nio有一定的了解&#xff0c;文章中…

MySQL索引优化二

分页查询优化 很多时候我们的业务系统实现分页功能可能会用如下sql实现 select * from employees limit 10000,10;表示从表employees中取出从10001行开始的10条记录.看似只查询了10条记录,实际这条sql是先读取10010条记录,然后抛弃前10000条记录,然后读到后面10条想要的数据,…

创建云知识库,你需要了解这些

云知识库对于提升企业的知识管理水平和整体效能具有重要作用。越来越多的企业使用云知识库。但是&#xff0c;对于刚开始创建云知识库的企业来说&#xff0c;什么是云知识库&#xff1f;云知识库的作用是什么&#xff1f;如何创建云知识库&#xff1f;这就是这篇文章要解决的问…

浏览器导出excel

做java web项目时&#xff0c;经常遇到需要在页面上点击导出按钮&#xff0c;然后直浏览器接下载下来一个excel文档。 比如一个List<Person>的集合&#xff0c;需要将每个Person当做一行&#xff0c;输出到excel中去。其中Person实体类如下&#xff1a; import lombok.…

ChatGPT助力论文写作:详细步骤解析

前言 在论文写作过程中&#xff0c;尽管人工智能工具如ChatGPT能为我们提供有效的辅助&#xff0c;但我们必须铭记&#xff0c;这些工具并不能完全取代我们的思考与判断能力。本指南将详尽地展示如何利用ChatGPT辅助论文写作的全过程&#xff0c;旨在帮助您更高效地完成学术任…

hadoop安装及基本使用

环境准备 三台centos7虚拟机&#xff0c;设置固定ip&#xff08;自己设置&#xff09;&#xff0c;设置ssh秘密登录&#xff08;自己设置&#xff09;&#xff0c;安装jdk8&#xff08;自己安装&#xff09; 准备安装包hadoop-3.3.6.tar.gz 位置在/home/hadoop 准备服务器之间…

Ubuntu 下统计文件数量的命令

参考:https://blog.csdn.net/kxh123456/article/details/123811580 查看当前目录下的文件数量&#xff08;不包含子目录中的文件&#xff09; ls -l|grep "^-"| wc -l实例展示&#xff1a;如下图所示&#xff0c;当前路径下&#xff0c;有2个json文件和2个文件夹&a…

JavaWeb学习笔记01

一、教程简介 全新JAVAWEB&#xff08;里程碑版&#xff09; 一套更适合后端工程师学习的WEB教程 All in Java 1、后端 ① Spring全家桶及微服务框架 ② 高性能数据库和消息组件 ③ Web攻击防护安全控制手段 ④ 其他第三方SDK生态环境 ...... 2、前端 ① 视图三大件&…

第十一届蓝桥杯大赛第二场省赛试题 CC++ 研究生组-寻找2020

数据很恶心&#xff0c;但是考点挺友好~ 把测试数据黏贴到记事本中&#xff0c;知测试数据的行列数 然后根据规则判断2020是否出现&#xff0c;并累计其次数即可。 判断可能需要注意超出下标&#xff0c;可以索性把数组定大些。 #include<stdio.h> const int N 310; ch…