爆款云主机2核4G限时秒杀,88元/年起!
查看详情

活动

天翼云最新优惠活动,涵盖免费试用,产品折扣等,助您降本增效!
热门活动
  • 618智算钜惠季 爆款云主机2核4G限时秒杀,88元/年起!
  • 免费体验DeepSeek,上天翼云息壤 NEW 新老用户均可免费体验2500万Tokens,限时两周
  • 云上钜惠 HOT 爆款云主机全场特惠,更有万元锦鲤券等你来领!
  • 算力套餐 HOT 让算力触手可及
  • 天翼云脑AOne NEW 连接、保护、办公,All-in-One!
  • 中小企业应用上云专场 产品组合下单即享折上9折起,助力企业快速上云
  • 息壤高校钜惠活动 NEW 天翼云息壤杯高校AI大赛,数款产品享受线上订购超值特惠
  • 天翼云电脑专场 HOT 移动办公新选择,爆款4核8G畅享1年3.5折起,快来抢购!
  • 天翼云奖励推广计划 加入成为云推官,推荐新用户注册下单得现金奖励
免费活动
  • 免费试用中心 HOT 多款云产品免费试用,快来开启云上之旅
  • 天翼云用户体验官 NEW 您的洞察,重塑科技边界

智算服务

打造统一的产品能力,实现算网调度、训练推理、技术架构、资源管理一体化智算服务
智算云(DeepSeek专区)
科研助手
  • 算力商城
  • 应用商城
  • 开发机
  • 并行计算
算力互联调度平台
  • 应用市场
  • 算力市场
  • 算力调度推荐
一站式智算服务平台
  • 模型广场
  • 体验中心
  • 服务接入
智算一体机
  • 智算一体机
大模型
  • DeepSeek-R1-昇腾版(671B)
  • DeepSeek-R1-英伟达版(671B)
  • DeepSeek-V3-昇腾版(671B)
  • DeepSeek-R1-Distill-Llama-70B
  • DeepSeek-R1-Distill-Qwen-32B
  • Qwen2-72B-Instruct
  • StableDiffusion-V2.1
  • TeleChat-12B

应用商城

天翼云精选行业优秀合作伙伴及千余款商品,提供一站式云上应用服务
进入甄选商城进入云市场创新解决方案
办公协同
  • WPS云文档
  • 安全邮箱
  • EMM手机管家
  • 智能商业平台
财务管理
  • 工资条
  • 税务风控云
企业应用
  • 翼信息化运维服务
  • 翼视频云归档解决方案
工业能源
  • 智慧工厂_生产流程管理解决方案
  • 智慧工地
建站工具
  • SSL证书
  • 新域名服务
网络工具
  • 翼云加速
灾备迁移
  • 云管家2.0
  • 翼备份
资源管理
  • 全栈混合云敏捷版(软件)
  • 全栈混合云敏捷版(一体机)
行业应用
  • 翼电子教室
  • 翼智慧显示一体化解决方案

合作伙伴

天翼云携手合作伙伴,共创云上生态,合作共赢
天翼云生态合作中心
  • 天翼云生态合作中心
天翼云渠道合作伙伴
  • 天翼云代理渠道合作伙伴
天翼云服务合作伙伴
  • 天翼云集成商交付能力认证
天翼云应用合作伙伴
  • 天翼云云市场合作伙伴
  • 天翼云甄选商城合作伙伴
天翼云技术合作伙伴
  • 天翼云OpenAPI中心
  • 天翼云EasyCoding平台
天翼云培训认证
  • 天翼云学堂
  • 天翼云市场商学院
天翼云合作计划
  • 云汇计划
天翼云东升计划
  • 适配中心
  • 东升计划
  • 适配互认证

开发者

开发者相关功能入口汇聚
技术社区
  • 专栏文章
  • 互动问答
  • 技术视频
资源与工具
  • OpenAPI中心
开放能力
  • EasyCoding敏捷开发平台
培训与认证
  • 天翼云学堂
  • 天翼云认证
魔乐社区
  • 魔乐社区

支持与服务

为您提供全方位支持与服务,全流程技术保障,助您轻松上云,安全无忧
文档与工具
  • 文档中心
  • 新手上云
  • 自助服务
  • OpenAPI中心
定价
  • 价格计算器
  • 定价策略
基础服务
  • 售前咨询
  • 在线支持
  • 在线支持
  • 工单服务
  • 建议与反馈
  • 用户体验官
  • 服务保障
  • 客户公告
  • 会员中心
增值服务
  • 红心服务
  • 首保服务
  • 客户支持计划
  • 专家技术服务
  • 备案管家

了解天翼云

天翼云秉承央企使命,致力于成为数字经济主力军,投身科技强国伟大事业,为用户提供安全、普惠云服务
品牌介绍
  • 关于天翼云
  • 智算云
  • 天翼云4.0
  • 新闻资讯
  • 天翼云APP
基础设施
  • 全球基础设施
  • 信任中心
最佳实践
  • 精选案例
  • 超级探访
  • 云杂志
  • 分析师和白皮书
  • 天翼云·创新直播间
市场活动
  • 2025智能云生态大会
  • 2024智算云生态大会
  • 2023云生态大会
  • 2022云生态大会
  • 天翼云中国行
天翼云
  • 活动
  • 智算服务
  • 产品
  • 解决方案
  • 应用商城
  • 合作伙伴
  • 开发者
  • 支持与服务
  • 了解天翼云
      • 文档
      • 控制中心
      • 备案
      • 管理中心

      【C++】哈希unordered系列容器的模拟实现

      首页 知识中心 软件开发 文章详情页

      【C++】哈希unordered系列容器的模拟实现

      2024-05-28 08:15:10 阅读次数:41

      c++,哈希算法,数据结构

      一、哈希表的模拟实现(开散列)

      1. 开散列的概念

      开散列又叫链地址法(开链法),首先对关键码集合用散列函数计算散列地址,即 key 映射的下标位置,具有相同地址的关键码 (哈希冲突) 归于同一子集合,每一个子集合称为一个桶 (哈希桶),各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中;也就是说,当发生哈希冲突时,把 key 作为一个节点直接链接到下标位置的哈希桶中。

      【C++】哈希unordered系列容器的模拟实现

      在这里我们可以看到,开散列中每个桶中放的都是发生哈希冲突的元素;由于开散列的不同冲突之间不会互相影响——同一冲突都链接在自己下标位置的哈希桶中,并不会去占用别人的下标位置;所以不管是在插入还是在查找方面,开散列都比闭散列要高效,所以C++STL中的unordered_map和unordered_set容器以及Java中的HashMap和HashSet容器的底层哈希表都是使用开散列来实现的,只是某些细节方面有些不同罢了,所以开散列也是我们学习哈希表的重点。


      2. 开散列的节点结构

      由于开散列的不同冲突之间不会互相影响,所以开散列不再需要 state 变量来记录每个下表位置的状态;同时,因为开散列每个下标位置链接的都是一个哈希桶,所以 vector 中的每个元素都是一个节点的指针,指向单链表的第一个元素,所以 _tables 是一个指针数组;最后,为了是不同类型的 key 都能够计算出映射的下标位置,所以我们这里也需要传递仿函数;如下:

      template<class K,class V>
      struct HashNode
      {
      	pair<K, V> _kv;
      	HashNode<K, V>* _next;
      
      	HashNode(const pair<K,V>& kv)
      		:_kv(kv)
      		,_next(nullptr)
      	{}
      };
      
      //哈希表的仿函数
      template<class K>
      struct HashFunc
      {
      	size_t operator()(const K& key)
      	{
      		return key;
      	}
      };
      
      //类模板的特化
      template<>
      struct HashFunc<string>
      {
      	size_t operator()(const string& key)
      	{
      		size_t sum = 0;
      		for (auto ch : key)
      			sum = sum * 131 + ch;
      		return sum;
      	}
      };
      
      template<class K,class V,class Hash = HashFunc<K>>
      class HashTable
      {
      	typedef HashNode<K, V> Node;
      private:
      	vector<Node*> _tables; //指针数组
      	size_t _n = 0; //有效存储的数据个数
      };
      

      3. 开散列的插入删除与查找

      💕 开散列的插入

      开散列的插入的前半部分和闭散列一样,根据key与哈希表大小得到映射的下标位置,与闭散列不同的是,由于哈希表中每个下标位置都是一个哈希桶,即一个单链表,所以对于发现哈希冲突的元素我们只需要将其链接到哈希桶中即可,这里一共有两种链接方式:

      • 将发生冲突的元素链接到单链表的末尾,即尾插
      • 将发生冲突的元素链接到单链表的开头,即头插

      在这里我们还需要考虑一下开散列的扩容问题。

      由于开散列桶的个数是一定的,即哈希表的长度,所以随着元素的不断插入,每个桶中元素的个数会不断增多;那么在极端情况下,可能会导致一个桶中链表的节点非常多,这样会影响哈希表的性能 – 查找与删除效率变低,因此在一定条件下需要对哈希表进行增容;

      那么增容条件该怎么确认呢?开散列最好的情况是每个哈希桶中刚好挂一个节点,再继续插入元素时,每一次都会发生哈希冲突;因此,在元素个数刚好等于桶的个数时,可以给考虑哈希表增容,即载荷因子为1时。

      这里我们给出了两种扩容的办法:

      一种是闭散列的方法 – 通过复用 insert 函数接口来进行扩容,但是这种扩容方法的效率很低,因为我们将旧表节点插入到新表时需要重新开辟节点,在插入并交换完毕后,我们又需要释放掉旧表中的节点,而 new 和 delete 的代价是很大的,特别是当 KV 是自定义类型时;

      所以这里我们选择第二组方法进行扩容 – 取出旧表中的每个节点链接到新表中,然后再交换旧表与新表;这样做就减少了 new 和 delete 的过程,大大提高了扩容的效率。(注:这里不能将原表中的整个哈希桶链接到新表中,因为新表的大小改变后原表中的元素可能会映射到新表的其他位置)

      同时,开散列的析构函数是需要我们自己实现的,因为默认生成的析构函数并不会释放掉哈希桶。

      bool Insert(const pair<K, V>& kv)
      {
      	if (Find(kv.first))
      		return false;
      	Hash hash;
      
      	//负载因子 == 1时扩容
      	if (_n == _tables.size())
      	{
      		//这种方法不太好,一个一个的插入,会导致效率及其低下,而且还需要释放掉原来的节点
      
      		/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
      		HashTable<K,V> newht;
      		newht._tables.resize(newsize);
      		for (auto cur : _tables)
      		{
      			while (cur)
      			{
      				newht.Insert(cur->_kv);
      				cur = cur->_next;
      			}
      		}
      		_tables.swap(newht._tables);*/
      
      		size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
      		vector<Node*>newtables(newsize, nullptr);
      		for (auto& cur : _tables)
      		{
      			while (cur)
      			{
      				Node* next = cur->_next;
      				size_t hashi = hash(cur->_kv.first) % newtables.size();
      
      				//头插到新表
      				cur->_next = newtables[hashi];
      				newtables[hashi] = cur;
      				cur = next;
      			}
      		}
      		_tables.swap(newtables);
      
      	}
      	size_t hashi = hash(kv.first) % _tables.size();
      	//头插
      	Node* newnode = new Node(kv);
      	newnode->_next = _tables[hashi];
      	_tables[hashi] = newnode;
      
      	++_n;
      	return true;
      }
      

      💕 开散列的查找

      先根据余数找到下标,由于下标位置存储的是首元素的地址,所以我们只需要取出首元素的地址,然后遍历单链表查找即可。

      //查询
      Node* Find(const K& key)
      {
      	if (_tables.size() == 0)
      		return nullptr;
      
      	Hash hash;
      	size_t hashi = hash(key) % _tables.size();
      	Node* cur = _tables[hashi];
      	while (cur)
      	{
      		if (cur->_kv.first == key)
      			return cur;
      		cur = cur->_next;
      	}
      	return nullptr;
      }
      

      💕 开散列的删除

      开散列的删除不能直接通过查找函数的返回值来进行删除,因为单链表在删除结点的时候还需要改变父节点的指向,让其指向目标节点的下一个节点,所以我们通过遍历单链表的方式来进行删除。

      //删除
      bool Erase(const K& key)
      {
      	Hash hash;
      	size_t hashi = hash(key) % _tables.size();
      	Node* prev = nullptr;
      	Node* cur = _tables[hashi];
      	while (cur)
      	{
      		if (cur->_kv.first == 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;
      }
      

      4. 开散列整体代码实现

      //开散列实现哈希表——拉链法
      namespace HashBucket
      {
      	template<class K,class V>
      	struct HashNode
      	{
      		pair<K, V> _kv;
      		HashNode<K, V>* _next;
      
      		HashNode(const pair<K,V>& kv)
      			:_kv(kv)
      			,_next(nullptr)
      		{}
      	};
      
      	//哈希表的仿函数
      	template<class K>
      	struct HashFunc
      	{
      		size_t operator()(const K& key)
      		{
      			return key;
      		}
      	};
      
      	//类模板的特化
      	template<>
      	struct HashFunc<string>
      	{
      		size_t operator()(const string& key)
      		{
      			size_t sum = 0;
      			for (auto ch : key)
      				sum = sum * 131 + ch;
      			return sum;
      		}
      	};
      
      	template<class K,class V,class Hash = HashFunc<K>>
      	class HashTable
      	{
      		typedef HashNode<K, V> Node;
      	public:
      		//插入
      		bool Insert(const pair<K, V>& kv)
      		{
      			if (Find(kv.first))
      				return false;
      			Hash hash;
      
      			//负载因子 == 1时扩容
      			if (_n == _tables.size())
      			{
      				//这种方法不太好,一个一个的插入,会导致效率及其低下,而且还需要释放掉原来的节点
      
      				/*size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
      				HashTable<K,V> newht;
      				newht._tables.resize(newsize);
      				for (auto cur : _tables)
      				{
      					while (cur)
      					{
      						newht.Insert(cur->_kv);
      						cur = cur->_next;
      					}
      				}
      				_tables.swap(newht._tables);*/
      
      				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
      				vector<Node*>newtables(newsize, nullptr);
      				for (auto& cur : _tables)
      				{
      					while (cur)
      					{
      						Node* next = cur->_next;
      						size_t hashi = hash(cur->_kv.first) % newtables.size();
      
      						//头插到新表
      						cur->_next = newtables[hashi];
      						newtables[hashi] = cur;
      						cur = next;
      					}
      				}
      				_tables.swap(newtables);
      
      			}
      			size_t hashi = hash(kv.first) % _tables.size();
      			//头插
      			Node* newnode = new Node(kv);
      			newnode->_next = _tables[hashi];
      			_tables[hashi] = newnode;
      
      			++_n;
      			return true;
      		}
      
      		//查询
      		Node* Find(const K& key)
      		{
      			if (_tables.size() == 0)
      				return nullptr;
      
      			Hash hash;
      			size_t hashi = hash(key) % _tables.size();
      			Node* cur = _tables[hashi];
      			while (cur)
      			{
      				if (cur->_kv.first == key)
      					return cur;
      				cur = cur->_next;
      			}
      			return nullptr;
      		}
      
      		//删除
      		bool Erase(const K& key)
      		{
      			Hash hash;
      			size_t hashi = hash(key) % _tables.size();
      			Node* prev = nullptr;
      			Node* cur = _tables[hashi];
      			while (cur)
      			{
      				if (cur->_kv.first == 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;
      		}
      
      	private:
      		vector<Node*> _tables; //指针数组
      		size_t _n = 0; //有效存储的数据个数
      	};
      	void TestHashTable1()
      	{
      		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
      		HashTable<int, int> ht;
      		for (auto e : a)
      		{
      			ht.Insert(make_pair(e, e));
      		}
      
      		ht.Insert(make_pair(15, 15));
      		ht.Insert(make_pair(25, 25));
      		ht.Insert(make_pair(35, 35));
      		ht.Insert(make_pair(45, 45));
      	}
      	void TestHashTable2()
      	{
      		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
      		HashTable<int, int> ht;
      		for (auto e : a)
      		{
      			ht.Insert(make_pair(e, e));
      		}
      
      		ht.Erase(12);
      		ht.Erase(3);
      		ht.Erase(33);
      	}
      }
      

      这里还存在一个问题,在我们上面的实现中,哈希桶是一个单链表,并且当哈希表的载荷因子等于1时,我们就进行扩容,这样在大多数情况下,每个哈希桶中的节点数都在1~2个,最坏情况下应该也不会超过 3~5 节点;这样我们查找时每次经过常数次查找就能够找到,即查找效率为 O(1);

      但是在某些极端情况下,或者面对某些碰撞攻击时 (对方如果知道了你哈希表的长度即除数,可能会专门向你传递属于同一冲突的数据,比如全部为哈希表长度的倍数),那么就会导致单链表过长,从而降低哈希表的查询与删除效率;

      为了应对这种情况,在 Java 8 中,如果一个桶中元素的数量超过了阈值,就会将其转换为红黑树,红黑树可以保证查找、插入和删除操作的时间复杂度都是 O(log n),比链表快得多,而对于较小的桶,仍然使用链表来存储元素;即 Java 8 中的 HashMap 使用红黑树来优化哈希冲突的极端情况,从而提高了 HashMap 的性能和效率。

      同样,C++11 也引入了一个新的数据结构 – 开放定址哈希表 (open addressing hash table),用于存储哈希冲突时的元素;开放定址哈希表是一种不使用链表来解决冲突的哈希表实现方式。在这种实现方式中,如果一个槽(slot)已经被占据了,就会尝试找到下一个可用的槽,直到找到一个空槽。因此,开放定址哈希表可以更好地利用缓存,从而提高查找性能。

      也就是说,在 C++11 及以后的版本中,unordered_map 的哈希桶使用了两种不同的数据结构,包括单链表和开放定址哈希表 – 当桶中元素数量较少时,使用链表;当桶中元素数量超过一定阈值时,会自动转换为开放定址哈希表。


      二、unordered系列容器的封装实现(开散列)

      1. 迭代器

      哈希表也需要单独定义一个类来实现迭代器,不过由于哈希表的迭代器是单向的,所以我们不用实现 operator–();其中,哈希表的 begin() 为第一个哈希桶中的第一个节点,即哈希表中第一个非空位置的数据,哈希表的 end() 这里我们定义为 nullptr;

      哈希表迭代器实现的难点在于 operator++,哈希表的迭代器 ++ 分为两种情况:

      • 当前哈希桶的下面还有节点,说明当前下标位置的哈希桶还没遍历完,此时迭代器++到当前哈希桶的下一个节点;
      • 当前哈希桶的下面没有节点,即cur->next==nullptr,说明当前下标位置的哈希桶已经遍历完了,此时迭代器++到哈希表的下一个非空位置,即下一个哈希桶。

      因为我们需要访问哈希表的_tables数组来确定下一个哈希桶的位置,所以要在迭代器类中定义一个HashTable类型的指针变量,同时,由于_tables是HashTable类的私有成员,所以我们还必须将HashTable类中将_HashTableIterator类声明为友元类,这样我们才能正确实现迭代器++的功能。

      💕 注意

      1. 由于我们在迭代器类中增加了一个哈希表的指针变量_ht,所以我们在HashTable中定义 begin() 和 end() 时除了要传递节点的指针来初始化_node,还需要传递this指针来初始化 _ht。
      2. 由于迭代器类中要定义HashTable类型的指针变量,同时HashTable类中又要typedef迭代器类型作为迭代器,所以在这里就存在相互引用的问题,为了解决这个问题,我们需要在迭代器类前面声明一下HashTable类。

      代码实现:

      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;
      	typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
      	typedef HashTable<K, T, KeyOfT, Hash> HT;
      	typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
      	
      	__HashIterator(Node* node, const 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;
      	}
      	
      	//重载 ++it
      	Self& operator++()
      	{
      		//判断下一个节点是否为nullptr
      		if (_node->_next)
      		{
      			_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;
      		}
      	}
      	
      	Node* _node;
      	const HT* _ht;
      };
      
      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;
      	
      	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;
      		for (auto& node : _tables)
      		{
      			cur = node;
      			if (cur)
      				break;
      		}
      		return iterator(cur, this);
      	}
      	
      	iterator end()
      	{
      		return iterator(nullptr, this);
      	}
      	
      	const_iterator begin() const
      	{
      		Node* cur = nullptr;
      		for (size_t i = 0; i < _tables.size(); ++i)
      		{
      			cur = _tables[i];
      			if (cur)
      			{
      				break;
      			}
      		}
      		return const_iterator(cur, this);
      	}
      	
      	const_iterator end() const
      	{
      		return const_iterator(nullptr, this);
      	}
      private:
      	vector<Node*> _tables; //指针数组
      	size_t _n = 0; //有效存储的数据个数
      }
      

      在哈希表中,我们继续使用增加模板参数Ref和Ptr来解决const迭代器问题。这里我们和前面的红黑树一样,使用哈希表迭代器类中构造函数来实现用普通迭代器来构造 const迭代器。

      typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
      __HashIterator(const Iterator& it)
      	:_node(it._node)
      	, _ht(it._ht)
      {}
      

      2. unordered_set和unordered_map的封装实现

      • 为了使哈希表能够同时封装 KV模型的 unordered_map 和 K模型的 unordered_set,哈希表不能将节点的数据类型直接定义为 pair< K, V >,而是需要通过参数 T 来确定;同时,由于 insert 函数在求余数时需要取出 T 中的 key 转化为整形,所以上层的 unordered_map 和 unordered_set 需要定义一个仿函数 MapKeyOfT 和 SetKeyOfT 来获取 key (主要是为了 unordered_map 而设计);

      • 为了保证 unordered_set 的 key 值不被修改,我们需要使用 哈希表 的 const 迭代器来封装 unordered_set 的普通迭代器,但是这样会导致哈希表的普通迭代器赋值给 const 迭代器的问题,所以我们需要将 unordered_set 的 begin() 和 end() 函数都定义为 const 成员函数;

      • 因为 unordered_map 的 operator 函数兼具插入、查找、和修改功能,所以如果我们要在模拟实现的 unordered_map 中重载 [] 运算符,就需要将 find 函数的返回值改为 iterator,将 insert 函数的返回值改为 pair\u003Citerator, bool>,并且要改的话 哈希表、unordered_map、unordered_set 这三个地方都要改。
        同时,unordered_set insert 函数的返回值变为 pair<iterator, bool> 后又会引发普通迭代器赋值给 const 迭代器的问题,所以对于 unordered_set 的 insert 函数,我们要先使用哈希表的普通迭代器构造的键值对去完成插入操作,然后再利用 普通迭代器来构造 const 迭代器进行返回;
        而要用普通迭代器构造 const 迭代器,我们又需要在哈希表的 const 迭代器类中增加一个类似于拷贝构造的函数,来将普通迭代器构造为 const 迭代器进行返回;

      💕 UnorderedSet

      //UnorderedSet
      #pragma once
      #include"HashTable.h"
      namespace cjl
      {	
      	template<class K,class Hash = HashFunc<K>>
      	class unordered_set
      	{
      	public:
      		struct SetKeyOfT
      		{
      			const K& operator()(const K& key)
      			{
      				return key;
      			}
      		};
      
      		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(key);
      		}
      
      		bool erase(const K& key)
      		{
      			return _ht.Erase(key);
      		}
      
      	private:
      		HashBucket::HashTable<K, K, SetKeyOfT, Hash> _ht;
      	};
      
      	void test_unordered_set1()
      	{
      		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
      		unordered_set<int> s;
      		for (auto e : a)
      		{
      			s.insert(e);
      		}
      
      		unordered_set<int>::iterator it = s.begin();
      		while (it != s.end())
      		{
      			//*it = 2;
      			cout << *it << " ";
      			++it;
      		}
      		cout << endl;
      	}
      
      	void test_unordered_set2()
      	{
      		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
      		unordered_set<int> s;
      		for (auto e : a)
      		{
      			s.insert(e);
      		}
      
      		s.insert(54);
      		s.insert(107);
      
      		unordered_set<int>::iterator it = s.begin();
      		while (it != s.end())
      		{
      			cout << *it << " ";
      			++it;
      		}
      		cout << endl;
      		s.erase(54);
      		s.erase(107);
      
      		for (auto e : s)
      		{
      			cout << e << " ";
      		}
      		cout << endl;
      
      		auto it2 = s.find(1002);
      		if (it2 != s.end())
      			cout << "找到了" << endl;
      		else
      			cout << "没找到" << endl;
      	}
      }
      

      💕 UnorderedMap

      #pragma once
      #include"HashTable.h"
      namespace cjl
      {	
      	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 HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;
      		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, 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 pair<K, V>& kv)
      		{
      			return _ht.Insert(kv);
      		}
      
      		iterator find(const K& key)
      		{
      			return _ht.Find(key);
      		}
      
      		bool erase(const K& key)
      		{
      			return _ht.Erase(key);
      		}
      
      		V& operator[](const K& key)
      		{
      			pair<iterator, bool> ret = insert({ key,V() });
      			return ret.first->second;
      		}
      
      	private:
      		HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
      	};
      	
      }
      

      UnorderedMap测试代码

      namespace cjl
      {
      	void test_unordered_map1()
      	{
      		int a[] = { 3, 33, 2, 13, 5, 12, 1002 };
      		unordered_map<int,int> s;
      		for (auto e : a)
      		{
      			s.insert({ e, e });
      		}
      
      		unordered_map<int, int>::iterator it = s.begin();
      		while (it != s.end())
      		{
      			cout << it->first << ":" << it->second << endl;
      			++it;
      		}
      		cout << endl;
      	}
      
      	void test_unordered_map2()
      	{
      		string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉", "梨" };
      		unordered_map<string, int> countMap;
      		for (auto& e : arr)
      		{
      			countMap[e]++;
      		}
      
      		for (auto& kv : countMap)
      		{
      			cout << kv.first << ":" << kv.second << endl;
      		}
      		cout << endl;
      
      		auto it = countMap.find("西瓜");
      		if (it != countMap.end())
      			cout << "找到了:" << it->first << ":" << it->second << endl;
      		else
      			cout << "没找到" << endl;
      		
      		countMap.erase("西瓜");
      
      		it = countMap.find("西瓜");
      		if (it != countMap.end())
      			cout << "找到了:" << it->first << ":" << it->second << endl;
      		else
      			cout << "没找到" << endl;
      	}
      
      	class Date
      	{
      		friend struct HashDate;
      	public:
      		Date(int year = 1900, int month = 1, int day = 1)
      			: _year(year)
      			, _month(month)
      			, _day(day)
      		{}
      
      		bool operator<(const Date& d)const
      		{
      			return (_year < d._year) ||
      				(_year == d._year && _month < d._month) ||
      				(_year == d._year && _month == d._month && _day < d._day);
      		}
      
      		bool operator>(const Date& d)const
      		{
      			return (_year > d._year) ||
      				(_year == d._year && _month > d._month) ||
      				(_year == d._year && _month == d._month && _day > d._day);
      		}
      
      		bool operator==(const Date& d) const
      		{
      			return _year == d._year
      				&& _month == d._month
      				&& _day == d._day;
      		}
      
      		friend ostream& operator<<(ostream& _cout, const Date& d);
      	private:
      		int _year;
      		int _month;
      		int _day;
      	};
      
      	ostream& operator<<(ostream& _cout, const Date& d)
      	{
      		_cout << d._year << "-" << d._month << "-" << d._day;
      		return _cout;
      	}
      
      	struct HashDate
      	{
      		size_t operator()(const Date& d)
      		{
      			size_t hash = 0;
      			hash += d._year;
      			hash *= 31;
      
      			hash += d._month;
      			hash *= 31;
      
      			hash += d._day;
      			hash *= 31;
      			return hash;
      		}
      	};
      
      	void test_unordered_map3()
      	{
      		Date d1(2023, 3, 13);
      		Date d2(2023, 3, 13);
      		Date d3(2023, 3, 12);
      		Date d4(2023, 3, 11);
      		Date d5(2023, 3, 12);
      		Date d6(2023, 3, 13);
      
      		Date a[] = { d1, d2, d3, d4, d5, d6 };
      
      		unordered_map<Date, int, HashDate> countMap;
      		for (auto e : a)
      		{
      			countMap[e]++;
      		}
      
      		for (auto& kv : countMap)
      		{
      			cout << kv.first << ":" << kv.second << endl;
      		}
      		cout << endl;
      		countMap.erase(d4);
      
      		for (auto& kv : countMap)
      		{
      			cout << kv.first << ":" << kv.second << endl;
      		}
      	}
      }
      

      3. 哈希表整体源码

      #pragma once
      
      template<class K>
      struct HashFunc
      {
      	size_t operator()(const K& key)
      	{
      		return key;
      	}
      };
      //模板的特化
      template<>
      struct HashFunc<string>
      {
      	size_t operator()(const string& str)
      	{
      		size_t hash = 0;
      		for (auto e : str)
      		{
      			hash += e;
      			hash *= 31;
      		}
      		return hash;
      	}
      };
      
      namespace HashBucket
      {
      	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 __HashIterator
      	{
      		typedef HashNode<T> Node;
      		typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
      		typedef HashTable<K, T, KeyOfT, Hash> HT;
      		typedef __HashIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;
      
      		__HashIterator(Node* node, const 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;
      		}
      		
      		//重载 ++it
      		Self& operator++()
      		{
      			//判断下一个节点是否为nullptr
      			if (_node->_next)
      			{
      				_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;
      			}
      		}
      
      		Node* _node;
      		const HT* _ht;
      	};
      
      	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;
      
      		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;
      			for (auto& node : _tables)
      			{
      				cur = node;
      				if (cur)
      					break;
      			}
      			return iterator(cur, this);
      		}
      
      		iterator end()
      		{
      			return iterator(nullptr, this);
      		}
      
      		const_iterator begin() const
      		{
      			Node* cur = nullptr;
      			for (size_t i = 0; i < _tables.size(); ++i)
      			{
      				cur = _tables[i];
      				if (cur)
      				{
      					break;
      				}
      			}
      			return const_iterator(cur, this);
      		}
      
      		const_iterator end() const
      		{
      			return const_iterator(nullptr, this);
      		}
      
      		~HashTable()
      		{
      			for (auto& node : _tables)
      			{
      				while (node)
      				{
      					Node* next = node->_next;
      					delete node;
      					node = next;
      				}
      				node = nullptr;
      			}
      		}
      
      		//插入
      		pair<iterator,bool> Insert(const T& data)
      		{
      			KeyOfT kot;
      			Hash hash;
      
      			iterator it = Find(kot(data));
      			if (it != end())
      				return { it, false };
      
      			//负载因子 == 1时扩容
      			if (_n == _tables.size())
      			{
      				size_t newsize = _tables.size() == 0 ? 10 : _tables.size() * 2;
      				vector<Node*>newtables(newsize, nullptr);
      				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 { iterator(newnode,this), true };
      		}
      
      		//查询
      		iterator Find(const K& key)
      		{
      			if (_tables.size() == 0)
      				return end();
      
      			KeyOfT kot;
      			Hash hash;
      			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)
      		{
      			KeyOfT kot;
      			Hash hash;
      			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;
      					_n--;
      					return true;
      				}
      				else
      				{
      					prev = cur;
      					cur = cur->_next;
      				}
      			}
      			return false;
      		}
      
      	private:
      		vector<Node*> _tables; //指针数组
      		size_t _n = 0; //有效存储的数据个数
      	};
      }
      

      版权声明:本文内容来自第三方投稿或授权转载,原文地址:https://chenjiale.blog.csdn.net/article/details/131333736,作者:星河万里᭄ꦿ࿐,版权归原作者所有。本网站转在其作品的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如因作品内容、版权等问题需要同本网站联系,请发邮件至ctyunbbs@chinatelecom.cn沟通。

      上一篇:第一季:14redis持久化【Java面试题】

      下一篇:给定一棵树的头节点head,原本是一棵正常的树, 现在,在树上多加了一条冗余的边, 请找到这条冗余的边并返回。

      相关文章

      2025-05-19 09:04:53

      查看RISC-V版本的gcc中默认定义的宏

      查看RISC-V版本的gcc中默认定义的宏

      2025-05-19 09:04:53
      c++ , linux
      2025-05-19 09:04:14

      《剑指Offer》按之字形顺序打印二叉树——最容易理解的思路,两分钟学会~

      《剑指Offer》按之字形顺序打印二叉树——最容易理解的思路,两分钟学会~

      2025-05-19 09:04:14
      二叉树 , 数据结构
      2025-05-19 09:04:14

      《剑指Offer》二叉搜索树的第k个节点——真没你想象中那么难,一招教你秒杀它~

      《剑指Offer》二叉搜索树的第k个节点——真没你想象中那么难,一招教你秒杀它~

      2025-05-19 09:04:14
      二叉树 , 数据结构
      2025-05-19 09:04:14

      【牛客网+LeetCode】链表 OJ强训题——高效解法

      【牛客网+LeetCode】链表 OJ强训题——高效解法

      2025-05-19 09:04:14
      数据结构 , 链表
      2025-05-09 08:20:32

      MySQL——索引(概述和结构介绍)

      索引(index)是帮助 MySQL 高效获取数据的数据结构(是一种有序的数据结构)。

      2025-05-09 08:20:32
      Tree , 存储 , 引擎 , 数据结构 , 查询 , 索引 , 结构
      2025-05-07 09:10:01

      DS初阶:顺序表的实现

      DS初阶:顺序表的实现

      2025-05-07 09:10:01
      函数 , 指针 , 数据 , 数据结构 , 数组 , 空间 , 顺序
      2025-04-18 07:11:40

      Java数据结构之《最短路径》

      Java数据结构之《最短路径》

      2025-04-18 07:11:40
      代码 , 数据结构 , 样例 , 路径 , 输入 , 输出 , 顶点
      2025-04-15 09:19:55

      Redis经典问题:BigKey问题

      在Redis中,每个Key都会对应一个Value,而这个Value的大小会影响Redis的性能表现。当我们存储的Value特别大时,就会出现BigKey问题。

      2025-04-15 09:19:55
      Key , Redis , 数据结构 , 系统 , 缓存 , 问题
      2025-04-15 09:19:45

      文心一言 VS 讯飞星火 VS chatgpt (263)-- 算法导论20.1 2题

      在Go语言中,为了支持带有卫星数据的关键字,我们可以定义一个结构体(struct)来表示这个关键字,其中可以包含一个字段用于存储关键字本身,以及另一个字段用于存储与该关键字相关联的卫星数据。

      2025-04-15 09:19:45
      关键字 , 存储 , 数据 , 数据结构
      2025-04-15 09:19:45

      文心一言 VS 讯飞星火 VS chatgpt (262)-- 算法导论20.1 1题

      在Go语言中,如果你想要一个数据结构支持重复的关键字(或键),你不能简单地使用内建的map,因为map在Go中是基于键的唯一性设计的。

      2025-04-15 09:19:45
      map , 关键字 , 数据结构 , 示例 , 重复
      查看更多
      推荐标签

      作者介绍

      天翼云小翼
      天翼云用户

      文章

      33561

      阅读量

      5224241

      查看更多

      最新文章

      Java数据结构之《最短路径》

      2025-04-18 07:11:40

      完全背包代码模板

      2025-04-14 09:24:23

      Python算法学习[8]—经典数据结构问题&具体实现

      2025-04-09 09:16:56

      Python算法学习[4]—树、二叉树、霍夫曼树&算法实现

      2025-04-09 09:16:56

      golang与 C++数据结构类型对应关系是怎样的?

      2025-04-01 10:29:12

      算法题:剑指 Offer 07. 重建二叉树(题目+思路+代码+注释)时空时间优先选O(N) O(N) 2ms击败97%、76%用户,空间优先则选O(N*logN) O(1)

      2025-03-31 08:49:48

      查看更多

      热门文章

      C/C++泛型编程实现数据结构之栈

      2023-05-15 10:00:33

      Lambda函数

      2023-02-08 10:33:56

      QT中多线程的使用

      2023-02-07 10:34:04

      0030 简单的四则运算 c/c++

      2023-03-21 10:39:47

      C++虚函数知识点总结

      2023-02-21 06:21:46

      非阻塞算法

      2023-03-21 10:32:27

      查看更多

      热门标签

      java Java python 编程开发 代码 开发语言 算法 线程 Python html 数组 C++ 元素 javascript c++
      查看更多

      相关产品

      弹性云主机

      随时自助获取、弹性伸缩的云服务器资源

      天翼云电脑(公众版)

      便捷、安全、高效的云电脑服务

      对象存储

      高品质、低成本的云上存储服务

      云硬盘

      为云上计算资源提供持久性块存储

      查看更多

      随机文章

      C# OpenCV9 haarcascade+cuda发送图片到gpu完成人体识别

      【C++】引用

      数据结构之 - 深入探析队列数据结构: 助你理解其原理与应用

      【C++】C++入门

      【C++】引用、内联函数、函数重载、函数默认参数(缺省参数)与占位参数、extern “C“ 浅析

      哈夫曼树之哈夫曼编码分析-算法设计与分析报告C/C++版

      • 7*24小时售后
      • 无忧退款
      • 免费备案
      • 专家服务
      售前咨询热线
      400-810-9889转1
      关注天翼云
      • 旗舰店
      • 天翼云APP
      • 天翼云微信公众号
      服务与支持
      • 备案中心
      • 售前咨询
      • 智能客服
      • 自助服务
      • 工单管理
      • 客户公告
      • 涉诈举报
      账户管理
      • 管理中心
      • 订单管理
      • 余额管理
      • 发票管理
      • 充值汇款
      • 续费管理
      快速入口
      • 天翼云旗舰店
      • 文档中心
      • 最新活动
      • 免费试用
      • 信任中心
      • 天翼云学堂
      云网生态
      • 甄选商城
      • 渠道合作
      • 云市场合作
      了解天翼云
      • 关于天翼云
      • 天翼云APP
      • 服务案例
      • 新闻资讯
      • 联系我们
      热门产品
      • 云电脑
      • 弹性云主机
      • 云电脑政企版
      • 天翼云手机
      • 云数据库
      • 对象存储
      • 云硬盘
      • Web应用防火墙
      • 服务器安全卫士
      • CDN加速
      热门推荐
      • 云服务备份
      • 边缘安全加速平台
      • 全站加速
      • 安全加速
      • 云服务器
      • 云主机
      • 智能边缘云
      • 应用编排服务
      • 微服务引擎
      • 共享流量包
      更多推荐
      • web应用防火墙
      • 密钥管理
      • 等保咨询
      • 安全专区
      • 应用运维管理
      • 云日志服务
      • 文档数据库服务
      • 云搜索服务
      • 数据湖探索
      • 数据仓库服务
      友情链接
      • 中国电信集团
      • 189邮箱
      • 天翼企业云盘
      • 天翼云盘
      ©2025 天翼云科技有限公司版权所有 增值电信业务经营许可证A2.B1.B2-20090001
      公司地址:北京市东城区青龙胡同甲1号、3号2幢2层205-32室
      • 用户协议
      • 隐私政策
      • 个人信息保护
      • 法律声明
      备案 京公网安备11010802043424号 京ICP备 2021034386号