封装unordered_map和unordered_set

news2024/11/17 9:30:25

   先前用红黑树封装出了map和set,现在就要用哈希来封装unordered_map和unordered_set(为了简化名称,后面称u_map和u_set),u_map和u_set在学习map时曾了解过,只知道是无序,我还在想,不能排序有啥用呢?原来用于查找数据效率高得很。有了封装map和set的经历,下面的封装就好理解多了,难点基本上都是迭代器和模板参数。

一 模板参数

从u_map和u_set传的模板参数来理解

下图为哈希表接收的模板参数

   本文的哈希是开散列的,因为开散列可以更好地解决哈希冲突,所以哈希表中存的就是每个桶中第一个节点的指针,而T就是节点用于存各种数据类型的,也就是说u_map和u_set给哈希表模板传的第二个模板参数就是想要哈希表节点存的类型。接来下看看两个容器在节点存的类型。

 节点类内部实现

template<class T>//T为节点所存数据
	struct HashNode
	{
		HashNode(const T& data)
			:_data(data)
			, _next(nullptr)
		{
			;
		}
		T _data;
		HashNode<T>* _next;
	};

u_map实例化的哈希表变量

  如下图,u_map存在节点的是一个pair至于为什么要对K加上const,我想如果了解过map和set的封装的同学肯定能猜到(后面会再提及)。

u_set实例化的哈希表变量

  而u_set要节点存的就是一个K类型的毕竟u_set和set是类似的,只存一个key值为了让节点可以用一个变量,例如T _data,既可以存u_set的一个key值,又可以存u_map的key和value这两个值,只能让key和value绑在一个结构体上,总不能让节点内多加一个变量吧,u_set又用不了那么多,不就浪费了吗。

综上,传给哈希表的第二个模板参数是节点存的数据的类型,可能是一个pair,也可能是一个K类型的key值。但是哈希表还有其余的模板参数,这就要结合哈希表的实现才能说清楚,如下。

HashBucketTable其余模板参数介绍

  第一个就简单了,是专门提供给find这类参数是const K&key的,而T已经介绍过了。

  而剩下的两个模板参数都在下面一行代码里了

   由于我们前面将u_map的key和value统一为一个值,不仅方便了节点类内变量存储,insert也只要插入一个值就好了,这就说明插入的元素kv可能是个K类型的,也可能是一个pair。

  如果是一个pair,  我们要取出里面的key,然后%_table.size()求下标,但是如果u_set调用insert,就可以直接取模,因为这时候的kv就是key, 这就导致这里对不同类型要做不同的处理,借鉴封装map的经验,我们可以用一个仿函数GetKey将key返回,但这也不意味着可以直接取模,如果key是字符串呢,这就要用到第四个模板参数,它就是将一些自定义类型转为整数,然后用于取模,这个仿函数介绍哈希成员函数时会出代码,所以这两个仿函数是互相配合。

map的GetKey

set的GetKey

二 使key不可被修改

   当我们大致了解了哈希表的模板参数,还有个大问题前面提了却未解决,u_map和u_set的key都应该是不能修改的,那我们如何实现呢?

1 使u_set的key不可修改

   一般来说普通迭代器可以修改节点内的数据,也就是说u_set的普通迭代器可以修改节点内的数据,那不就意味着可以修改key?这样显然是不行的,所以我们要让外部取得iterator定义出的变量具有const_iterator的特性,还是老套路,用typedef重命名来欺骗使用者。

临场发问:typename的作用是什么?

  上图中我们自认为是从HashBucketTable这个类内取一个类型,可编译器编译的时候哪知道,假设有一个静态变量a,

  访问静态变量a的方式:HashBucketTable<K, pair<const K, V>, GainMapKey>::a,这个形式和前面取iterator不是一模一样吗,编译器哪分得清,而且你类又没实例化,编译器又没办法核实,所以会报错,只能我们写个关键字typename,告诉编译器这是个类型,等实例化了再检查,现在先别报错。

2 使u_map的key不可修改

  先前没说为什么要给K加上const,现在可以说了,就是使得K不可修改。

   仅仅只是我博客这些功能,u_set也可以通过传const K的方法去实现iterator有const_iterator的作用,但是库里大佬肯定比我想的细致,应该是我没考虑到这种方式对其余功能的阻碍。

三 迭代器实现

  这个迭代器的实现则与先前的有些不同,因为当我们++后,如果到空节点了,要去下一个不为空的桶,此时迭代器存的节点指针是没办法找到下一个桶的,这意味着我们需要访问哈希表,所以应该增加一个成员,哈希表指针。

   还有个问题,我们需要从深入迭代器实现才可以解决,就是_ht调用返回的pair<iterator,bool>中的iterator和外面u_set的iterator是不同的,因为外部的iterator是const_iterator重定义的,而内部iterator就是普通迭代器,类型已经不匹配了,这是我们自己定义的类型,编译器无法强转,除非我们写了对应的构造函数,编译器才有可能偷偷帮我们转换一下,接来下就带着问题看看迭代器如何支持的这个类型转换。

迭代器代码

template<class K, class T,class KeyofT, class Hashfunc>
    class HashBucketTable;

要前置声明,不然迭代器内会不认识哈希表指针

 template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>
	struct HashIterator    K,KeyofT, Hashfunc是给哈希表的模板参数
	{
		typedef HashNode<T> Node;
		typedef HashIterator<K, T, Ptr, Ref, KeyofT, Hashfunc> Self; 
     重命名为Self, 方便下面使用,模板参数变量也只需要改这里

		typedef HashIterator<K, T, T*,T&,KeyofT, Hashfunc> iterator;

HashIterator(Node* node, const HashBucketTable<K, T, KeyofT, Hashfunc>* pht)
			:_node(node)
			, _pht(pht)
		{
			;
		}
		HashIterator(const iterator& it)  
			:_node(it._node)
			, _pht(it._pht)
		{
			;
		}
		Self& operator++()
		{
			KeyofT kot;
			Hashfunc hf;
			Node* cur = _node;
			if (cur->_next)  下一个节点不为空
			{
				_node = _node->_next;
			}
			else  去下一个桶找
			{
				size_t hashi = (hf(kot(cur->_data)) % _pht->_table.size()) + 1;
				while (hashi<_pht->_table.size())  防止往后找不为空的桶时越界
				{
					if(!_pht->_table[hashi])
					  hashi++;
					else
					{
						_node = _pht->_table[hashi];
						return *this;
					}
				}
				_node = nullptr;  先前还没返回,说明全是空桶,达到end位置
			}
			return *this;
		}
		Ref operator*()  通过控制Ptr和Ref来达到const迭代器的作用
		{
			return _node->_data;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		bool operator!=(const Self& it)
		{
			return _node != it._node;
		}
		bool operator==(const Self& it)
		{
			return _node == it._node;
		}
		Node* _node;
		const HashBucketTable<K, T, KeyofT, Hashfunc>* _pht; 哈希表指针
	};

 

   当HashIterator被实例化为const迭代器, 这个就可以给u_set的insert做类型转换使用,所以我们前面需要定义一个普通迭代器,就是为了这一步 

   有没有注意到这个成员加了const,首先加上const绝对是可以的,因为限制我们通过_pht这个指针修改哈希表也是合理的。

或许你会说,不对,是因为构造函数是用const的指针接收的。

  那就看看下面的,哈希表指针是用this指针初始化的,而this指针的类型是 T*const,这个const不限制赋值,限制了感觉反而不好,举个例子,int* const p1=&a; int*p2=p1;你p1不能修改自己的值,总不能让p2也不能修改自己的值吧,好像不太合理但如果是const迭代器,那this指针就是const T*const,所以构造函数需要加上const,否则无法匹配,也就是使得哈希表指针这个成员变量要加const。

 

最后我说一下我之前的一个问题,我想it是const类型的,它的成员也是const类型的,为什么就能赋值给Node* _node, 现在我想明白了,it._node这个的类型是Node*const的,由前面的,这个const不限制,所以可以赋值。

u_map封装代码

u_map的成员函数都是复用_ht变量的函数,比较有意思的就是[]的实现了。

template<class K,class V>
class Unordered_map
{
public:
	struct GainMapKey//用来返回pair中的key
	{
		const K& operator()(const pair<K, V>& data)
		{
			return data.first;
		}
	};
	typedef HashNode<pair<const K, V>> Node;
	typedef typename  HashBucketTable<K, pair<const K, V>, GainMapKey>::iterator iterator;
	typedef typename  HashBucketTable<K, pair<const K, V>, GainMapKey>::const_iterator const_iterator;
	
	pair<iterator,bool> insert(const pair<K,V>& p1)
	{
		return _ht.insert(p1);
	}
	bool Erase(const K& k1)
	{
		return _ht.Erase(k1);
	}
	iterator find(const K& key) 这里find和_ht.find()返回类型和下面的insert一样
                                类型是不匹配的,但是构造函数支持了这种类型转换。
	{
		return _ht.find(key);
	}
	iterator begin()
	{
		return _ht.begin();
	}
	iterator end()
	{
		return _ht.end();
	}
	const_iterator begin()const
	{
		return _ht.begin();
	}
	const_iterator end()const
	{
		return _ht.end();
	}
	V& operator[](const K& p1)
	{
		pair<iterator,bool> ret=_ht.insert(make_pair(p1,V()));
		return (*ret.first).second;
       
     ret.first取得是iterator,*解引用就返回_node->_data的引用,
     这个_data对于u_map而言就是一个pair,我们再取second就是[]需要返回的value  
	}
private:
	HashBucketTable<K, pair<const K,V>, GainMapKey> _ht; 偏特化
};

u_set封装代码

template<class K>
class Unordered_set
{
public:
	struct GainSetKey  为了map和set传模板参数保持一致,set也要传一个GainSetKey
	{
		const K& operator()(const K& data)
		{
			return data;
		}
	};
	typedef HashNode<K> Node;
	typedef	typename HashBucketTable<K,K, GainSetKey>::const_iterator iterator;
	typedef	typename HashBucketTable<K, K, GainSetKey>::const_iterator const_iterator;

	
	pair<iterator,bool> insert(const K& k1)
	{
		return _ht.insert(k1);
	}
	bool Erase(const K& k1)
	{
		return _ht.Erase(k1);
	}
	iterator find(const K& key)
	{
		return _ht.find(key);
	}
    
   只提供const版本的begin和end函数,就是为了_ht调用的begin和end函数返回的是const_iterator
	const_iterator begin()const
	{
		return _ht.begin();  
	}
	const_iterator end()const 
	{
		return _ht.end();
	}
private:
	HashBucketTable<K, K, GainSetKey> _ht; 偏特化
};

四  哈希类成员函数介绍

template<class K>
struct DefaultHashfunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct DefaultHashfunc<string>    将string转为整数的仿函数
{   
	size_t operator()(const string& s)
	{
		int hash = 1;
		for (auto e : s)
			hash = hash * 131 + e;  BKDR
		return (size_t)hash;
	}
};
	
	
template<class K, class T, class KeyofT, class Hashfunc = DefaultHashfunc<K>>
  class HashBucketTable
	{
	public:
		template<class K, class T, class Ptr, class Ref,class KeyofT, class Hashfunc>//友元类的声明
		friend	struct HashIterator;  
     HashIterator要访问HashBucketTable的私有成员,所以是
		   在HashBucketTable对HashIterator友元声明

         typedef HashNode<T> Node;
		typedef HashIterator<K, T, T*, T&, KeyofT, Hashfunc> iterator;
		typedef HashIterator<K, T,const T*,const T&, KeyofT, Hashfunc> const_iterator;
         通过传const T*,const T&来达到const迭代器的效果		

        HashBucketTable()
		{
			_table.resize(5);
		}

		  析构函数函数
		~HashBucketTable()
		{
			for (size_t i = 0; i < _table.size(); i++) 
			{
				Node* cur = _table[i]; 
				while (cur)       找到一个非空桶,遍历链表,delete释放节点
				{
					Node* next = cur->_next;
					_table[i] = next;
					delete cur;
					cur = next;
				}
			}
		}
		iterator begin()
		{
			for (size_t i = 0; i < _table.size(); i++)//遍历哈希表ht
			{
				Node* cur = _table[i];
				if (cur)  找到第一个不为空的桶
				{
					return iterator(cur, this);
				}
			}
			return iterator(nullptr, this);
		}
		iterator end()
		{
			return  iterator(nullptr, this);
		}
		const_iterator begin()const
		{
			for (size_t i = 0; i < _table.size(); i++)//遍历哈希表ht
			{
				Node* cur = _table[i];
				if (cur)//找到第一个不为空的桶
				{
					return  const_iterator(cur, this);
				}
			}
			return const_iterator(nullptr, this);
		}
		const_iterator end()const
		{
			return  const_iterator(nullptr, this);
		}

         拷贝构造函数
		HashBucketTable(const HashBucketTable<K, T, KeyofT, Hashfunc>& ht)
		{
			KeyofT kot;
			Hashfunc hf;
			_table.resize(ht._table.size());
			for (size_t i = 0; i < ht._table.size(); i++)//遍历哈希表ht
			{
				Node* cur = ht._table[i];
				while (cur)//遍历该桶
				{
					Node* newnode = new Node(cur->_data);
					size_t hashi = hf(kot(cur->_data)) % _table.size();
					newnode->_next = _table[hashi];
					_table[hashi] = newnode;
					cur = cur->_next;//去桶的下一个节点
				}
			}
		}
	
		iterator find(const K& key)  此处还是返回普通迭代器
		{
			Hashfunc hf;
			KeyofT kot;
			size_t hashi = hf(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				cur = cur->_next;
			}
			return iterator(nullptr,this);
		}
		bool Erase(const K& key)
		{
			KeyofT kot;
			Hashfunc hf;
            iterator ret=find(key);
            if(ret._node==nullptr)  没找到节点,返回false
             return false;
			size_t hashi = hf(key) % _table.size();
			Node* pre = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					if (pre == nullptr)//头删
					{
						_table[hashi] = cur->_next;//链接
					}
					else
					{
						pre->_next = cur->_next;
					}
					delete cur;
					--_size;//负载因子要--
					return true;
				}
				pre = cur;
				cur = cur->_next;
			}
			return false;  没找到,erase失败
		}
		pair<iterator,bool> insert(const T& kv)
		{
			KeyofT kot;
			Hashfunc hf;
			查找该节点是否已经存在
			iterator it = find(kot(kv));
			if (it._node)//该节点存在,返回该节点迭代器,并且插入失败
				return make_pair(it, false);
			//扩容
			if (_size >= _table.size())
			{
				size_t newsize = _table.size() * 2;
                
				vector<Node*> newtable;
				newtable.resize(newsize);
				for (size_t i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i]; 搬运旧表上的节点,省得自己new和delete
					while (cur)
					{
						Node* next = cur->_next;
						_table[i] = next;
						size_t hashi = hf(kot(cur->_data)) % newtable.size();//计算新表映射下标
						搬到新表
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						cur = next;//搬下个节点
					}
				}
                新旧表交换
				_table.swap(newtable);
			}
         
            正常插入节点
			size_t hasi = hf(kot(kv)) % _table.size();
			Node* newnode = new Node(kv);
			newnode->_next = _table[hasi];
			_table[hasi] = newnode;
			_size++;
			return make_pair(iterator(newnode,this),true);
		}
	private:
		vector<Node*> _table;
		size_t _size = 0;
	};

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

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

相关文章

架构师选择题--数据库技术

架构师选择题--数据库技术 三级模式-两级映像数据库设计函数依赖公理系统范式 数据库在选择题考查3到5分&#xff08;不超纲&#xff09; 案例分析每年会考察一道题目 三级模式-两级映像 逻辑独立性 物理独立性 数据库设计 了解每个阶段的产出 逻辑结构设计&#xff1a;将E-R图…

使用Scipy优化梯度下降问题

目 录 问题重述 附加问题 步骤实施 1.查看Scipy官网SciPy&#xff0c;找到优化有关的模块&#xff08;Optimize&#xff09; 2.研究多种优化策略&#xff0c;选择最符合代码的方案进行优化 3.minimize函数参数及其返回值 4.代码展示 5.结果展示 6.进一步优化 6.1对…

数字孪生、AR和VR如何改进数据中心设计

数据中心基础设施管理(DCIM)已存在多年&#xff0c;它在许多数据中心被广泛使用&#xff0c;但还没有普遍使用&#xff0c;由于两个因素&#xff0c;这种情况正在改变&#xff1a;数字化的概念正在普及&#xff0c;IT与运营技术(OT)系统(如建筑管理系统(BMS)和电源管理工具)的集…

Leetcode 151. 反转字符串中的单词 JS版两种方法(内置API,双指针)有详细讲解 小白放心食用

&#x1f3b6;Leetcode 151. 反转字符串中的单词 难度&#xff1a;中等 ✨题目描述&#xff1a; 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 …

使用4090显卡部署 Qwen-14B-Chat-Int4

使用4090显卡部署 Qwen-14B-Chat-Int4 1. Qwen-Agent 概述2. Github 地址3. 创建虚拟环境4. 安装依赖项5. 快速使用6. 启动 web 演示7. 访问 Qwen 1. Qwen-Agent 概述 通义千问-14B&#xff08;Qwen-14B&#xff09; 是阿里云研发的通义千问大模型系列的140亿参数规模的模型。…

cartographer(1)-运行

1.下载数据集 #1.下载数据集&#xff1a; mkdir /home/tang/bagfiles#2.开始二维建图 cd /home/tang/carto_ws/cartographer_detailed_comments_ws/install_isolated/source install_isolated/setup.bash rospack profile #新装的包索引地址存在ros的环境里 roslaunch ca…

【Python】读取显示pgm图像文件

文章目录 零. 前言一. pgm基本概念二. pgm基本信息读取三. pgm图像渲染四. 代码优化 零. 前言 这学期要学多媒体信息隐藏对抗&#xff0c;发现其中的图像数据集文件都是pgm文件形式的。虽然是图像文件&#xff0c;但是却不能直接通过图像查看器来打开&#xff0c;上网一搜&…

【有限域除法】二元多项式除法电路原理及C语言实现

二元多项式除法电路原理 例: g ( x ) = x 4 + x 2 + x + 1 g(x)=x^4 + x^2+x+1

C语言-字符串与输入输出

一、字符串 1、字符串简介 在 C 语言中&#xff0c;字符串实际上是使用空字符 \0 结尾的一维字符数组。因此&#xff0c;\0 是用于标记字符串的结束。 空字符&#xff08;Null character&#xff09;又称结束符&#xff0c;缩写 NUL&#xff0c;是一个数值为 0 的控制字符&…

深入了解“注意力”和“变形金刚” -第1部分

一、说明 这是一篇很长的文章&#xff0c;几乎讨论了人们需要了解的有关注意力机制的所有信息&#xff0c;包括自我注意、查询、键、值、多头注意力、屏蔽多头注意力和转换器&#xff0c;包括有关 BERT 和 GPT 的一些细节。因此&#xff0c;我将本文分为两部分。在本文中&#…

【JavaEE】JUC(Java.util.concurrent)常见类

文章目录 前言ReentrantLock原子类线程池信号量CountDownLatch相关面试题 前言 经过前面文章的学习我们大致了解了如何实现多线程编程和解决多线程编程中遇到的线程不安全问题&#xff0c;java.util.concurrent 是我们多线程编程的一个常用包&#xff0c;那么今天我将为大家分…

消息驱动 —— SpringCloud Stream

Stream 简介 Spring Cloud Stream 是用于构建消息驱动的微服务应用程序的框架&#xff0c;提供了多种中间件的合理配置 Spring Cloud Stream 包含以下核心概念&#xff1a; Destination Binders&#xff1a;目标绑定器&#xff0c;目标指的是 Kafka 或者 RabbitMQ&#xff0…

一款支持功能安全车规级 线性PMIC稳压器 NCV4274CDS50R4G 解决方案:高效率、更智能、强功能安全

关于车规级芯片&#xff1a; 关于车规级芯片&#xff08;Automotive Grade Chip&#xff09;&#xff0c;车规级芯片是专门用于汽车行业的芯片&#xff0c;具有高可靠性、高稳定性和低功耗等特点&#xff0c;以满足汽车电子系统的严格要求。这些芯片通常用于车载电子控制单元&…

c++使用ifstream和ofstream报错:不允许使用不完整的类型

学习《C Primer》关于IO库的部分&#xff0c;输入284页的的代码&#xff0c;出现了报错&#xff1a; 不允许使用不完整的类型 原来的代码&#xff1a; #include <iostream> #include <vector> using namespace std;int main(int argc, char **argv) {ifstream in…

如何搭建一个 websocket

环境: NodeJssocket.io 4.7.2 安装依赖 yarn add socket.io创建服务器 引入文件 特别注意: 涉及到 colors 的代码&#xff0c;请采取 console.log() 打印 // 基础老三样 import http from "http"; import fs from "fs"; import { Server } from &quo…

分享几个优秀开源免费管理后台模版,建议收藏!

大家好&#xff0c;我是 jonssonyan 今天和大家分享一些免费开源的后台管理页面&#xff0c;帮助大家快速搭建前端页面。为什么要用模板&#xff1f;道理很简单&#xff0c;原因是方便我们快速开发。我们不应该花太多的时间在页面调整上&#xff0c;而应该把精力放在核心逻辑和…

关于滑块验证码的问题

这里写自定义目录标题 一、超级鹰二、图片验证模拟登录1、页面分析1.1、模拟用户正常登录流程1.2、识别图片里面的文字 2、代码实现 三、滑块模拟登录1、页面分析2、代码实现&#xff08;通过对比像素获取缺口位置&#xff09; 四、openCV1、简介2、代码3、案例 五、selenium 反…

Vue中如何进行数据库操作与数据持久化

在Vue中进行数据库操作与数据持久化 Vue.js作为一个流行的JavaScript框架&#xff0c;通常用于构建前端应用程序&#xff0c;但它本身并不提供数据库操作或数据持久化的功能。数据库操作通常由后端服务器处理&#xff0c;而Vue负责呈现和交互。然而&#xff0c;您可以使用Vue与…

P1-Python编辑器的选择和安装

1、Python编辑器的选择、安装及配置&#xff08;PyCharm、Jupyter&#xff09; PyCharm的安装&#xff1a; https://www.jetbrains.com/pycharm/PyCharm的配置&#xff1a; 1、创建新的项目 2、导入本地已有的Pytorch anaconda环境 配置环境中问题&#xff1a; https://bl…

Kitchen Racks

厨房置物架 完美&#xff01;&#xff01;&#xff01;