【C++】unordered_set 和 unordered_map 使用 | 封装

news2025/1/19 14:11:35

文章目录

  • 1. 使用
      • 1. unordered_set的使用
      • 2. unordered_map的使用
  • 2. 封装
    • 修改结构定义
    • 针对insert参数 data的两种情况
      • 复用 哈希桶的insert
      • KeyOfT模板参数的作用
    • 迭代器
      • operator++()
      • begin
      • end
      • unordered_set对于 begin和end的复用
      • unordered_map对于 begin和end的复用
      • unordered_map中operator[]的实现
      • unordered_set修改迭代器数据 问题
    • 完整代码
      • HashTable.h
      • unordered_set.h
      • unordered_map.h

1. 使用

unordered_map官方文档


unordered_set 官方文档


set / map与unordered_set / unordered_map 使用功能基本相同,但是两者的底层结构不同

set/map底层是红黑树
unordered_map/unordered_set 底层是 哈希表


红黑树是一种搜索二叉树,搜索二叉树又称为排序二叉树,所以迭代器遍历是有序的
而哈希表对应的迭代器遍历是无序的


在map中存在rbegin以及rend的反向迭代器


在unordered_map中不存在rbegin以及rend的反向迭代器


1. unordered_set的使用

大部分功能与set基本相同,要注意的是使用unordered_set是无序的

插入数据,并使用迭代器打印,会按照插入的顺序输出,但若插入的数据已经存在,则会插入失败

2. unordered_map的使用

map统计时,会按照ASCII值排序


而unordered_map 中 元素是无序的

2. 封装

对于 unordered_set 和 unordered_map 的封装 是针对于 哈希桶HashBucket进行的,
即 在哈希开散列 的基础上修改

点击查看:哈希 开散列具体实现

修改结构定义


哈希桶HashBucket中
需要将其内部的HashNode 的参数进行修改
将原来的模板参数 K,V 改为 T
同样由于不知道传入数据的是K还是K V类型的 ,所以 使用 T 类型的data代替


之前实现的模板参数 K ,V分别代表 key 与value
修改后 , 第一个参数 拿到单独的K类型,是为了 使用 Find erase接口函数的参数为K
第二个参数 T 决定了 拿到的是K类型 还是 K V 类型

针对insert参数 data的两种情况

创建 unordered_set.h头文件,在其中创建命名空间unordered_set
在unordered_set中实现一个仿函数,
unordered_set 的第二个参数 为 K
unordered_set作为 K 模型 ,所以 T应传入K


在这里插入图片描述

创建 unordered_map.h头文件,在其中创建命名空间unordered_map

unordered_map 的第二个参数 为 pair<cosnt K,V> 类型
K加入const ,是为了防止修改key
unordered_map 作为 KV 模型 ,所以 T应传入 pair<K,V>

复用 哈希桶的insert

在这里插入图片描述

在unordered_set中insert 是 复用HashTable中的insert
但实际上直接运行就会出现一大堆错误,哈希桶的insert 原本参数从kv变为 data,
由于data的类型为T,也就不知道 到底是unoreder_set 的K还是 unoreder_map 的KV类型的K
所以 insert中的 hashi的 key 不能够直接求出


KeyOfT模板参数的作用

假设为unordered_set,则使用kot对象调用operator(),返回的是key


假设为unordered_map,则使用kot对象调用operator(),返回的是KV模型中的key

迭代器


在迭代器内存存储 节点的指针 以及 哈希表

在迭代器中使用哈希表,在哈希表中使用迭代器 ,存在互相引用,需要使用前置声明


对于 operator * / operator-> / operator!= 跟 list 模拟实现基本类似

详细点击查看:list模拟实现


在自己实现的迭代器__HashIterator中,添加参数 Ref 和 Ptr

在这里插入图片描述

当为普通迭代器时,Ref作为T& ,Ptr作为T*


在这里插入图片描述
当为const 迭代器时,Ref作为const T& ,Ptr作为const T*


operator++()

调用 hash调用对应的仿函数HashFunc,若为普通类型(int)则输出对应key
若为 string类型,则调用特化,输出对应的各个字符累加的ASCII值

调用 KeyOfT ,主要区分unordered_map 与unordered_set ,
若为unordered_set ,则输出 K类型的K
若为unordered_map ,则输出 KV类型的K


hashi++,计算下一个桶的位置,判断是否为空,若不为空则将其作为_node
若为空,需要继续寻找不为空的桶


begin

在这里插入图片描述

在HashTable内部实现 begin,使用自己实现的_hashiterator 作为迭代器


在这里插入图片描述
返回第一个不为空的桶的第一个数据


c

end

在HashTable内部实现 end
返回最后一个桶的下一个位置 即nullptr

unordered_set对于 begin和end的复用

在unordered_set中,使用哈希桶中的HashTable的迭代器 来实现unordered_set的迭代器
加入typename 是因为编译器无法识别HashBucket::HashTable<K, K, SetKeyOfT>是静态变量还是类型


_ht作为哈希表,使其调用哈希表中的begin和end 来实现 unordered_set的begin 和end

unordered_map对于 begin和end的复用

在 unordered_map中使用哈希桶中的HashTable的迭代器 来实现unordered_map的迭代器


unordered_map中operator[]的实现

将insert的返回值 变为pair类型,第一个参数为迭代器 ,第二个参数为布尔值
若返回成功,则调用新插入位置的迭代器


通过寻找哈希桶中是否有相同的数据,若有则返回该迭代器以及false


在unordered_map中实现operator[]

详细查看:operaor[]本质理解

unordered_set修改迭代器数据 问题

在这里插入图片描述
在unordered_set中,借助 哈希桶中的普通迭代器 实现 unordered_set的普通迭代器
在unordered_set中,借助 哈希桶中的const迭代器 实现 unordered_set的const迭代器


在STL中,是不允许 unordered_set去 *it 修改数据的 ,但是在自己实现的迭代器中却可以通过


在这里插入图片描述

在STL中将 unordered_set的普通迭代器也为哈希桶的const 迭代器


调用begin时,虽然看似返回普通迭代器,但是当前普通迭代器是作为哈希桶的const迭代器存在的
返回值依旧是 哈希桶的普通迭代器


在哈希桶自己实现的迭代器__HashIterator中

创建一个普通迭代器 ,当传入普通迭代器时,为拷贝构造
当传入 const 迭代器时,就 发生隐式类型转换,讲普通迭代器转换为const 迭代器


此时在unordered_set中 的普通迭代器无法被修改

完整代码

HashTable.h


#include<iostream>
#include<vector>
using namespace std;

template<class K>
struct HashFunc //仿函数
{
	size_t operator()(const K& key)
	{
		return key;
	}
};

//特化
template<>
struct HashFunc<string> //仿函数
{
	//将字符串转化为整形
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto ch : s)
		{
			hash += ch;
			hash *= 31;//用上次的结果乘以31
		}
		return hash;
	}
};

namespace HashBucket //哈希桶
{
	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 Ref,class Ptr,class KeyOfT, class Hash >
	struct __HashIterator
	{
		typedef HashNode<T> Node;
		Node* _node;//节点的指针
		typedef HashTable<K, T, KeyOfT, Hash> HT; //哈希表 typedef HT
		HT* _ht; //哈希表
		typedef __HashIterator<K, T, Ref,Ptr,KeyOfT, Hash> Self; 
		
		//定义一个普通迭代器
		typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;

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

		__HashIterator(const Iterator &it)
			:_node(it._node),_ht(it._ht)
		{
		}

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

		Ptr operator->()
		{
		  return &_node->_data;
		}
		bool operator!=(const Self &s)
		{
		   //使用节点的指针进行比较
		   return _node != s._node;
		}

		//前置++
		Self& operator++()
		{
			//若不处于当前桶的尾位置,则寻找桶的下一个位置
			if (_node->_next != nullptr)
			{
				_node = _node->_next;
			}
			else
			{
				//若为空,则说明处于当前桶的尾位置,则需寻找下一个不为空的桶
				KeyOfT kot;
				Hash hash;
				//计算当前桶位置
				size_t hashi = hash(kot(_node->_data)) % _ht->_tables.size();
				hashi++;//下一个位置

				//寻找下一个不为空的桶
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])
					{
						_node = _ht->_tables[hashi];
						break;
					}
					else
					{
						//桶为空,需要继续寻找
						hashi++;
					}
				}

				//没有找到不为空的桶
				if (hashi == _ht->_tables.size())
				{
					_node = nullptr;
				}
			}
			return  *this;
		}
		
	};


	
	
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		template<class K, class T,class Ref,class Ptr, class KeyOfT, class Hash >
		friend struct   __HashIterator;//友元 使 __HashIterator中可以调用HashTable的私有
		typedef HashNode<T> Node;
	public:
		typedef __HashIterator<K, T,T&,T*, KeyOfT, Hash> iterator;
		typedef __HashIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
		

		iterator begin()
		{
			Node* cur = nullptr;
			size_t i = 0;
			//找到第一个不为空的桶
			for (i = 0; i < _tables.size(); i++)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}
			//迭代器返回 第一个桶 和哈希表
			//this 作为哈希表本身
			return iterator(cur,this);
		}

		iterator end()
		{
			return iterator(nullptr, this);
		}

		const_iterator begin()const
		{
			Node* cur = nullptr;
			size_t i = 0;
			//找到第一个不为空的桶
			for (i = 0; i < _tables.size(); i++)
			{
				cur = _tables[i];
				if (cur)
				{
					break;
				}
			}
			//迭代器返回 第一个桶 和哈希表
			//this 作为哈希表本身
			return const_iterator(cur, this);
		}	

		const_iterator end() const
		{
			return const_iterator(nullptr, this);
		}
		~HashTable()//析构
		{
			for (auto& cur : _tables)
			{
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				cur = nullptr;
			}
		}

		iterator Find(const K& key)//查找
		{
			Hash hash;
			KeyOfT kot;

			//若表刚开始为空
			if (_tables.size() == 0)
			{
				return end();
			}
			size_t hashi = hash(key) % _tables.size();
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur,this);
				}
				cur = cur->_next;
			}
			return end();
		}


		bool Erase(const K& key)//删除
		{
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _tables.size();
			Node* prev = nullptr;//用于记录前一个节点
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (prev == nullptr)//要删除节点为头节点时
					{
						_tables[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					return true;
				}
				else
				{
					prev = cur;
					cur = cur->_next;
				}
			}
			return false;
		}

		pair<iterator,bool> insert(const T& data )//插入
		{
			KeyOfT kot;
			//若对应的数据已经存在,则插入失败
			iterator it = Find(kot(data));
			if (it != end())
			{
				return make_pair(it, false);
			}

			Hash  hash;
			
			//负载因子==1时扩容
			if (_n == _tables.size())
			{
				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
				vector<Node*>newtables(newsize, nullptr);//创建 newsize个数据,每个数据都为空

				for (auto& cur : _tables)
				{
					while (cur)
					{
						Node* next = cur->_next;//保存下一个旧表节点
						size_t hashi = hash(kot(cur->_data)) % newtables.size();
						//将旧表节点头插到新表节点中
						cur->_next = newtables[hashi];
						newtables[hashi] = cur;

						cur = next;
					}
				}
				_tables.swap(newtables);
			}


			size_t hashi = hash(kot(data)) % _tables.size();

			//头插
			Node* newnode = new Node(data);//新建节点
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
		   return make_pair(iterator(newnode,this), true);
		   
		}
	private:
		vector<Node*> _tables;  //指针数组
		size_t _n=0;//存储数据有效个数
	};
}



unordered_set.h


#include"HashTable.h"
namespace yzq
{
	template<class K,class Hash=HashFunc<K>>
	class unordered_set
	{
	public:
		struct SetKeyOfT//仿函数
		{
			const K& operator()(const K&key)
			{
				return key;
			}
		};
	public:

	typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::const_iterator  iterator;
	typedef typename HashBucket::HashTable<K, K, SetKeyOfT, Hash>::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 K&key)
		{
			return _ht.insert(key);
		}

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

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		HashBucket::HashTable<K,K, SetKeyOfT, Hash> _ht;
	};

	void test_set()
	{
		unordered_set<int> v;
		v.insert(1);
		v.insert(3);
		v.insert(2);
		v.insert(8);
		v.insert(1000);
		v.insert(5);
		
		unordered_set<int>::iterator it = v.begin();
		while (it != v.end())
		{
			//*it = 1;
			cout << *it << " ";
			++it;
		}
	}
}

unordered_map.h


#include"HashTable.h"
namespace yzq
{
	template<class K,class V,class Hash=HashFunc<K>>
	class unordered_map
	{
	public:
		struct MapKeyOfT//仿函数
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
	public:
	typedef typename HashBucket::HashTable<K,
		pair<const K, V>, MapKeyOfT,Hash>::iterator  iterator;

	typedef typename HashBucket::HashTable<K, 
		pair<const K, V>, MapKeyOfT, Hash>::constiterator  const_iterator;
	iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}

		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key,V()));
			return ret->first->second;
		}

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

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

		bool erase(const K& key)
		{
			return _ht.Erase(key);
		}
	private:
		HashBucket::HashTable<K, pair<const K ,V>, MapKeyOfT,Hash>  _ht;

	};
	void test_map()
	{
		unordered_map<int,int> v;
		v.insert(make_pair(1,1));
		v.insert(make_pair(3, 3));
		v.insert(make_pair(10, 10));
		v.insert(make_pair(2, 2));
		v.insert(make_pair(8, 8));
		unordered_map<int, int>::iterator it = v.begin();
		while (it != v.end())
		{
			cout << it->first<<":"<<it->second << " ";
			++it;
		}
		cout << endl;

	}
}

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

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

相关文章

计组期末复习---个人版

&#xff08;一&#xff09;计算机系统概论 1.1计算机分类与发展历史 分类&#xff1a;电子模拟计算机和电子数字计算机 电子模拟计算机&#xff1a;数值由连续量来表示&#xff0c;运算过程是连续的 电子数字计算机&#xff1a;按位运算&#xff0c;并且不是连续地跳动运算…

【JavaSE】Java基础语法(二十四):时间日期类

文章目录 1. Date类2. Date类常用方法3. SimpleDateFormat类&#xff08;应用&#xff09; 1. Date类 计算机中时间原点 1970年1月1日 00:00:00 时间换算单位 1秒 1000毫秒 Date类概述 Date 代表了一个特定的时间&#xff0c;精确到毫秒 Date类构造方法 示例代码 publi…

数据结构-顺序表

数据结构-顺序表 线性表顺序表的概念和结构静态顺序表和动态顺序表 接口的实现顺序表的初始化顺序表的打印顺序表的销毁顺序表的增容顺序表的尾插顺序表的尾删顺序表的头插顺序表的头删顺序表的任意位置插入顺序表的任意位置删除顺序表中元素的查找 完整代码 线性表 线性表是n…

数据包伪造替换、会话劫持、https劫持之探索和测试

&#xff08;一&#xff09;数据包替换攻击 该攻击过程如下&#xff1a;伪造服务器响应客户端的数据包。监听客户端的数据包&#xff0c;用预先伪造的数据包&#xff0c;伪装成服务器返回的数据发送给客户端。 因为攻击者跟目标在同一个局域网&#xff0c;所以攻击者发送的数…

无监督学习——k均值

文章目录 聚类k均值代码实现1. 引入依赖2. 数据加载3. 算法实现4. 测试 无监督学习重要的应用有两类&#xff1a;聚类、降维。 聚类&#xff1a; k均值 基于密度的聚类 最大期望聚类 降维&#xff1a; 潜语义分析&#xff08;LSA&#xff09; 主成分分析&#xff08;PCA&a…

AcWing算法提高课-1.3.11二维费用的背包问题

宣传一下算法提高课整理 <— CSDN个人主页&#xff1a;更好的阅读体验 <— 本题链接&#xff08;AcWing&#xff09; 点这里 题目描述 有 N N N 件物品和一个容量是 V V V 的背包&#xff0c;背包能承受的最大重量是 M M M。 每件物品只能用一次。体积是 v i v_…

【C++系列P3】‘类与对象‘-三部曲——[精讲](1/3)

前言 大家好吖&#xff0c;欢迎来到 YY 滴 C系列 &#xff0c;热烈欢迎&#xff01; 【 类与对象-三部曲】的大纲主要内容如下&#xff1a; 如标题所示&#xff0c;本章是【 类与对象-三部曲】三章中的第一章节——基础知识章节&#xff0c;主要内容如下&#xff1a; 目录 一.…

apache-jmeter:点击可视化界面闪退和中文乱码Failed to write core dump

目录 1、点击界面闪退1.1、问题描述1.2、解决方法 2、处理返回结果乱码问题3、中文界面乱码3.1、问题描述3.2、解决方法 1、点击界面闪退 1.1、问题描述 Java运行环境 $ java -version java version "1.8.0_251" Java(TM) SE Runtime Environment (build 1.8.0_25…

学生成绩管理系统(C语言有结构体实现)

目录标 一、要实现的功能1.首次运行2. 成绩录入3. 显示录入的成绩4. 计算平均值5. 对平均分排序6. 查询学生成绩7. 清屏8. 显示帮助菜单9. 系统 二、实现代码&#xff08;一&#xff09;所有代码在一个文件&#xff08;v1&#xff09;&#xff08;二&#xff09;分文件编写&…

全志V3S嵌入式驱动开发(制作根文件系统)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 根文件系统是嵌入式开发很重要的一环。目前对于大多数soc来说&#xff0c;制作嵌入式系统就是配置buildroot文件。今天使用的buildroot版本是build…

一个字牛!腾讯大牛把《数据结构与算法》讲透了,带源码笔记

话不多说&#xff0c;直接先上图 经历过校招的人都知道&#xff0c;算法和数据结构都是不可避免的。 在笔试的时候&#xff0c;最主要的就是靠算法题。像拼多多、头条这种大公司&#xff0c;上来就来几道算法题&#xff0c;如果你没AC出来&#xff0c;面试机会都没有。 在面试…

上午在改BUG,下午就被通知优化了····

前段时间&#xff0c;爱奇艺被曝出大规模裁员的消息&#xff0c;裁员比例为20%-40%&#xff0c;对此&#xff0c;爱奇艺并未回应。有多位爱奇艺员工向深燃证实了裁员消息。“现在&#xff0c;空了好些工位。”一位爱奇艺员工表示。据他了解&#xff0c;仅爱奇艺文学&#xff0c…

21天学会C++:Day5----引用

CSDN的uu们&#xff0c;大家好。这里是C入门的第五讲。 座右铭&#xff1a;前路坎坷&#xff0c;披荆斩棘&#xff0c;扶摇直上。 博客主页&#xff1a; 姬如祎 收录专栏&#xff1a;C专题 目录 1. 知识引入 2. 引用的特性 2.1 引用在定义时必须初始化 2.2 一个变量可以有多…

车间静电消除不掉?静电接地桩来帮忙!

静电接地桩的原理是通过将金属导体与地面相连&#xff0c;以便在设备运行时能够稳定地将静电荷自然地释放到地面中&#xff0c;从而保护人员和设备不受到静电的危害。 在工业生产中&#xff0c;静电容易在人体和物体表面积聚&#xff0c;如果不及时地排放处理会对人员和设备造…

【算法】简单讲解如何使用两个栈实现一个队列

文章目录 什么是栈和队列&#xff1f;设计思路代码实现 什么是栈和队列&#xff1f; 栈和队列其实大家基本都知道是什么&#xff0c;或者说&#xff0c;最基本的&#xff0c;他们的特性我们是知道的。 栈是一种FILO先进后出的数据结构&#xff0c;队列是一种FIFO先进先出的数据…

SRS流媒体服务器 ---- st-thread框架

1.使用st-thread 我们用一个简单的demo研究一下st框架。 #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include "st.h"static void *_thread(void *arg) {printf("thread: %lu\n", pthread_self());return NULL; }i…

实例6.1 六度空间

“六度空间”理论又称作“六度分隔&#xff08;Six Degrees of Separation&#xff09;”理论。这个理论可以通俗地阐述为&#xff1a;“你和任何一个陌生人之间所间隔的人不会超过六个&#xff0c;也就是说&#xff0c;最多通过五个人你就能够认识任何一个陌生人。”如图1所示…

MySql MVCC 详解

注意以下操作都是以InnoDB引擎为操作基准。 一&#xff0c;前置知识准备 1&#xff0c;MVCC简介 MVCC 是多版本并发控制&#xff08;Multiversion Concurrency Control&#xff09;的缩写。它是一种数据库事务管理技术&#xff0c;用于解决并发访问数据库的问题。MVCC 通过创…

ROS学习——Gazebo中搭建模型并显示

一、打开gazebo搭建模型 gazebo 在gazebo界面左上角点击“Edit”——>"Building Editor"进入下图的模型搭建界面。可以自己利用左边的材料搭建模型。 点击墙壁之类的物品&#xff0c;右键&#xff0c;点击“Open Wall Inspector”按钮&#xff0c;就会出现可以调…

jmeter做接口压力测试_jmeter接口性能测试

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是免…