C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map

2023-10-27

哈希表模板参数改造

针对模板参数V改造

因为不同容器的类型不同,如果是unordered_map,V代表一个键值对,如果unordered_set,V代表Key值,而底层哈希表并不知道上层容器所要传递的V模板参数类型,为了与哈希表的模板参数区分,我们将哈希表中的V模板参数改为T.
在这里插入图片描述

增加仿函数获取具体数据类型.

例如: 在哈希表中当我们使用Find函数进行查找时:
如果上层传递的数据类型为Key值,我们就可以直接与查找数据比较.
如果上层传递的数据类型为pair<K,V>键值对,我们就不可以直接比较,需要利用仿函数获取键值对的first进行比较.
unordered_set仿函数SetKeyOfT代码:

struct SetKeyOfT              //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

unordered_map仿函数MapKeyOfT代码:

struct MapKeyOfT                   //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;   //获取pair<K,V>键值对中的first.
			}
		};

所以,我们要在哈希表模板参数中增加一个仿函数KeyOfT.
如果是unordered_set,就传 SetKeyOfT类型的仿函数.
如果是unordered_map,就传MapKeyOfT类型的仿函数.

模板参数转换图示如下:
在这里插入图片描述
额外说明:
1: 我们对原先哈希表中的Hash进行了修改,不要缺省值,因为我们肯定是使用上层容器传递不同的仿函数类型,所以在unordered_set与unordered_map中传递仿函数类型.

哈希表的正向迭代器

正向迭代器中的内置成员:

哈希表正向迭代器有两个内置成员:
1: 结点指针
2: 哈希表的指针


template < class K, class T, class Hash, class KeyOfT >
class HashTable;                   //重新声明哈希表类模板.
//正向迭代器
template<class K, class T, class KeyOfT, class HashFunc = Hash<K>>
struct __HTIterator
{
	typedef HashNode<T> Node; //重新定义哈希结点的类型为Node.
	typedef HashTable<K, T, KeyOfT, HashFunc> HT; //重新定义哈希表的类型为HT.
	typedef __HTIterator<K, T, KeyOfT, HashFunc> Self; //重新定义正向迭代器的类型为Self.

	Node* _node; //结点指针
	HT* _pht; //哈希表指针
};

注意:
因为迭代器中要使用到哈希表类型,所以我们在哈希表迭代器前面必须声明一下哈希表类模板.

正向迭代器的成员函数

以下成员函数较为简单,就不另外说明:


		_HashIterator( Node* node,HT* pht )  //初始化列表
			:_node(node)
			, _pht(pht)
		{
		}
        
		T& operator*()                     //解引用运算符重载
		{
			return _node->_data;
		}

		T* operator->()                 //->运算符重载
		{
			return &_node->_data;
		}

		bool operator!=( const Self& s ) const  //外面增加const可以使const对象也能调用该成员函数.
		{
			return _node != s._node;  //实质上是迭代器中的_node比较
		}


		bool operator==( const Self& s )const 
		{
			return _node == s._node;
		}

前置++运算符重载:
前置++运算符重载主要有两个步骤:
1: 如果当前结点指针的下一个结点存在,则结点指针指向下一个结点.

2: 如果当前结点指针的下一个结点不存在:
(1): 通过哈希函数计算哈希桶的位置.

(2): 在哈希表中,从哈希桶中的下一个位置开始遍历查找.
如果哈希桶存在,则将结点指针指向该哈希桶.
如果哈希表遍历完还没有找到,则说明这是哈希表中的最后一个数据的迭代器,则结点指针指向空,表明最后一个数据的下一个结点.

Self& operator++()
		{
			if (_node->_next)  //在当前桶中迭代
			{
				_node = _node->_next;
			}
			                  //如果该哈希桶迭代完毕,则在哈希表中找下一个哈希桶迭代寻找.
			else
			{
				KeyOfT kot;
				Hash hash;
				size_t hashi = hash(kot(_node->_data)) % _pht->_table.size(); //迭代器访问哈希表的私有成员
				size_t i = hashi + 1;
				
				for (;  i < _pht->_table.size(); ++i)
				{
					if (_pht->_table[i])
					{
						_node = _pht->_table[i];
						break;                       //++完就退出.
					}
			        
				}
				if (i == _pht->_table.size())        //此时,这也说明正式哈希桶迭代器的end.
				{
					_node = nullptr;
				}

			}

			return *this;
		}

哈希表插入函数的修改(适用于unordered_map)

为了支持unordered_map中的[]操作符重载,我们针对哈希表中的插入函数返回值进行修改
(1): 如果哈希表中已有所给数据,则返回已有数据的迭代器与插入结果(失败)所构成的键值对.

(2): 如果哈希表没有所给数据,则将新数据头插该哈希桶组成的单链表上,返回新插入结点指针构造与插入结果(成功)组成的键值对.

插入函数代码如下:


	      pair< Iterator,bool> Insert(const T& data)
		   {
			Hash hash;
			KeyOfT kot;		                                                   
			Iterator ret = Find(kot(data));                    
			if (ret != end())
			{
				return make_pair( ret, false);
			}
			if (_size == _table.size())                                //扩容.
			{
				//	size_t newSize = _table.size() == 0 ? 10 : 2 * _table.size();
				vector<Node*> newTable;
				newTable.resize(GetNextPrime(_table.size()), nullptr); 

				Hash hash;
				//从旧表结点移动映射新表.
				for (size_t i = 0; i < _table.size(); ++i)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

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

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
               
			}
			size_t hashi = hash(kot(data)) % _table.size();
			Node* newNode = new Node(data);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			++_size;
			return make_pair(Iterator(newNode,this),true);                //如果是新插入的数据,则返回新插入结点构造的迭代器与插入结果组成的pair.
		}

一个类型K去做set和unordered_set他的模板参数的必备条件.

set容器模板参数:
(1):set模板参数要求能够支持小于比较,例如:二叉搜索树查找成员函数中,我们可以利用compare仿函数将当前结点值与所给值比较,从而决定cur遍历左子树还是右子树,为了能够支持大于比较的,我们可以交换一下实参位置,这也间接支持了大于比较.并且条件判断,进而也支持了等于.
unordered_set容器模板参数要求:
(1)针对于K类型(指针,打浮点数,有符号整型)可以转换为无符号整数取模.对于string,日期类类型,这两个类型与整型类型无关的,此时不可以直接强转为整数,需要相应的提供将该类型转换为整数的仿函数.

(2):K类型对象能够支持等于比较(因为在查找中就是要确定存储的对象与所传的对象是否相等,例如Data日期类),如果不支持需要额外提供等于比较的仿函数.

unordered_set的模拟实现(完整代码)

在上述中,我们已经将unordered_set重点内容讲解完毕,剩下的成员函数只需要复用哈希表中的就可以了.

namespace yzh
{
	//2:因为我们使用map,set是将实参传给,map,set的所以当string类型取模时要在map,set中写仿函数.
	template < class K,class Hash = HashFunc<K>> //1:由第二个模板参数决定要存储的数据.
	class unordered_set
	{
		struct SetKeyOfT                                  //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashBucket::HashTable< K,K,Hash,SetKeyOfT>::Iterator iterator;  //重新定义一下迭代器类型为iterator.

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		
		pair<iterator,bool> Insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		HashBucket::HashTable<K,K,Hash,SetKeyOfT> _ht;
	};
	void test_set()                      //测试
	{
		unordered_set<int> set;
		set.Insert(1);
		set.Insert(2);
		set.Insert(3);
		
		unordered_set<int>::iterator it = set.begin();
		while ( it != set.end() )
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}
	
}

unordered_map的实现(完整代码)

在上述中,我们已经将unordered_map重点内容讲解完毕,剩下的成员函数只需要复用哈希表中的就可以了.

namespace yzh
{
	template < class K, class V, class Hash = HashFunc<K> >            //由第二个模板参数决定要存储的数据类型,
	class unordered_map
	{
		struct MapKeyOfT                                             //根据map还是set传递不同数据类型给底层.
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return kv.first;
			}
		};
		
	public:
		typedef typename HashBucket::HashTable< K, pair<K, V>, Hash, MapKeyOfT>::Iterator 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  )                      //给一个key,返回V的引用.
	   {
		   pair<iterator, bool> ret = _ht.Insert(make_pair(key, V()));   //如果插入失败,返回已有数据的迭代器与返回结果组成的pair.                                                 //如果插入成功,返回新插入数据的迭代器与返回结果组成的pair.
		   return ret.first->second;
	   }
	   
	private:
		HashBucket::HashTable<K, pair<K, V>, Hash, MapKeyOfT> _ht;     //map和set都知道传递什么来类型的仿函数,以及要传递的数据类型.
	};
	void test_map()
	{
		unordered_map<string, string> dict;
		dict.Insert(make_pair("sort", "排序"));
		dict.Insert(make_pair("string", "排序"));

		unordered_map<string,string>::iterator it = dict.begin();
		while (it != dict.end())
		{
			cout << it->first << ":" << it->second  << endl;
     		++it;
		}
	}

	void testmap1()
	{
		unordered_map<string, int> countMap;
		string arr[] = { "苹果","西瓜","苹果","西瓜","苹果","西瓜","苹果","香蕉" };
		for (auto& e : arr)
		{
			countMap[e]++;
		}

		for (auto& kv : countMap)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
    }


	
}

适用于unordered_set和unordered_map的哈希表代码

#include <map>
#include <vector>
#include <string>
#include <string>
#include <iostream>
using namespace std;
template < class K >                   //仿函数的默认值,如果不显示传就会默认调用.
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template< >                                 //1:对于常见类型,为了方便,我们可以对类模板进行特化.
struct HashFunc <string>                    //并且根据实参所传类型,优先走特化版本.	                                     
{
	size_t operator()(const string& key)
	{
		size_t  val = 0;
		for (auto& ch : key) //遍历string,将一个个字母替换成ASCI码进行相加.
		{
			val += ch;
		}
		return val;
	}
};
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 Hash, class KeyOfT >
	class HashTable;
	
	template < class K, class T, class Hash, class KeyOfT >
	struct _HashIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable< K, T, Hash, KeyOfT> HT;
		typedef _HashIterator< K, T, Hash, KeyOfT> Self;
		Node* _node;
	    HT* _pht;
		
		_HashIterator( Node* node,HT* pht )
			:_node(node)
			, _pht(pht)
		{
		}
        
		T& operator*()
		{
			return _node->_data;
		}

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

		Self& operator++()
		{
			if (_node->_next)  //在当前桶中迭代
			{
				_node = _node->_next;
			}
			                  //如果该哈希桶迭代完毕,则在哈希表中找下一个哈希桶迭代寻找.
			else
			{
				KeyOfT kot;
				Hash hash;
				size_t hashi = hash(kot(_node->_data)) % _pht->_table.size(); //迭代器访问哈希表的私有成员
				size_t i = hashi + 1;
				
				for (;  i < _pht->_table.size(); ++i)
				{
					if (_pht->_table[i])
					{
						_node = _pht->_table[i];
						break;                       //++完就退出.
					}
			        
				}
				if (i == _pht->_table.size())        //此时,这也说明正式哈希桶迭代器的end.
				{
					_node = nullptr;
				}

			}

			return *this;
		}

		bool operator!=( const Self& s ) const
		{
			return _node != s._node;
		}


		bool operator==( const Self& s )const 
		{
			return _node == s._node;
		}

		
	};
	
	template < class K, class T, class Hash, class KeyOfT >
	struct HashTable
	{
		typedef HashNode<T> Node;
		template < class K, class T, class Hash, class KeyOfT > //为了迭代器能够访问哈希表的私有成员,我们设为迭代器是哈希表的
		                                                        //友元,类模板声明友元需要增加类模板.
		friend struct  _HashIterator;
		
	public:
		typedef _HashIterator< K, T, Hash, KeyOfT> Iterator;
		
		Iterator begin()//获取第一个迭代器
		{
			for (size_t i = 0; i < _table.size(); ++i)
			{
				if (_table[i])
				{
					return Iterator( _table[i], this );
				}
			}
			return end();
		}

		Iterator end()  //获取最后一个迭代器
		{
			return Iterator(nullptr, this);
		}

		size_t GetNextPrime(size_t prime)
		{
			const int PRIMECOUNT = 28;
			//素数序列
			const size_t primeList[PRIMECOUNT] =
			{
				53, 97, 193, 389, 769,
				1543, 3079, 6151, 12289, 24593,
				49157, 98317, 196613, 393241, 786433,
				1572869, 3145739, 6291469, 12582917, 25165843,
				50331653, 100663319, 201326611, 402653189, 805306457,
				1610612741, 3221225473, 4294967291
			};
			size_t i = 0;
			for (i = 0; i < PRIMECOUNT; i++)
			{
				if (primeList[i] > prime)         //从这个数组里面取第一个大于prime的值.
					return primeList[i];
			}
			return primeList[i];                 //虽然数据不可能那么大,但是也有可能不会走if判断,
												 // 所以从语法上来说还要考虑所给值prime大于素数数组整个数据的情况.
		}

		~HashTable()         //vvector会调用析构函数,但是哈希桶必须自己写.
		{
			for (size_t i = 0; i < _table.size(); ++i)
			{
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					cur = next;
				}
				_table[i] = nullptr;
			}
			//	cout << "~HushTable()" << endl;

		}

		bool  Erase(const K& key)
		{
			if (_table.size() == 0)
			{
				return true;
			}
			Hash hash;
			size_t hashi = hash(key) % _table.size();
			Node* prev = nullptr;
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_kv.first == key)
				{
					if (prev == nullptr)
					{
						_table[hashi] = cur->_next;
					}
					else
					{
						prev->_next = cur->_next;
					}
					delete cur;
					--_size;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	      pair< Iterator,bool> Insert(const T& data)
		   {
			Hash hash;
			KeyOfT kot;
			
			                                                   //去重
			Iterator ret = Find(kot(data));                    //如果该数据已经有了,则返回已有数据的迭代器与插入结果构成的pair.
			if (ret != end())
			{
				return make_pair( ret, false);
			}
			if (_size == _table.size())                                //扩容.
			{
				//	size_t newSize = _table.size() == 0 ? 10 : 2 * _table.size();
				vector<Node*> newTable;
				newTable.resize(GetNextPrime(_table.size()), nullptr);    //扩容

				Hash hash;
				//从旧表结点移动映射新表.
				for (size_t i = 0; i < _table.size(); ++i)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;

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

						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newTable);
               
			}
			size_t hashi = hash(kot(data)) % _table.size();
			Node* newNode = new Node(data);
			newNode->_next = _table[hashi];
			_table[hashi] = newNode;
			++_size;
			return make_pair(Iterator(newNode,this),true);                //如果是新插入的数据,则返回新插入结点构造的迭代器与插入结果组成的pair.
		}

	     Iterator Find(const K& key)
		{
			if (_table.size() == 0)        //表为空,就返回nullptr.
			{
				return end();
			}
			Hash hash;
			KeyOfT kot;
			size_t hashi = hash(key) % _table.size();
			Node* cur = _table[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return Iterator(cur, this);
				}
				cur = cur->_next;
			}
			return end();
		}

		size_t size()
		{
			return _size;
		}
		size_t TablesSize()          //表的长度
		{
			return  _table.size();
		}

		size_t BucketNum()           //有多少个桶被用了.
		{
			size_t Num = 0;
			for (size_t i = 0; i < _table.size(); ++i)
			{
				if (_table[i])
				{
					++Num;
				}
			}
			return Num;
		}
		size_t MaxBucketLenth()
		{
			size_t maxLen = 0;
			for (size_t i = 0; i < _table.size(); ++i)
			{
				size_t len = 0;
				Node* cur = _table[i];
				while (cur)
				{
					++len;
					cur = cur->_next;
				}
				if (len > maxLen)
				{
					maxLen = len;
				}
			}
			return maxLen;
		}
	private:
		vector<Node*> _table;
		size_t _size = 0;
	};
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map 的相关文章

随机推荐

  • 机器学习——卷积神经网络基础

    卷积神经网络 Convolutional Neural Network CNN 卷积神经网络是人工神经网络的一种 是一种前馈神经网络 最早提出时的灵感来源于人类的神经元 通俗来讲 其主要的操作就是 接受输入层的输入信息 通过卷积层 激活层
  • Pinia刷新会丢失数据 应该怎么解决

    Pinia pinia官网 pinia plugin persistedstate pinia plugin persistedstate官网 Pinia 是一个 Vue js 状态管理库 它允许你创建一个全局的 store 和多个模块化的
  • 不同页面参数值传递localStorage

    不同页面参数值传递localStorage 介绍 localStorage 和 sessionStorage 属性允许在浏览器中存储 key value 对的数据 localStorage 用于长久保存整个网站的数据 保存的数据没有过期时间
  • C语言-qsort函数详解

    一 qsort函数是什么 qsort 函数 快速排序的函数 引用stdlib h头文件 参数说明 void qsort void base 要排序的目标数组 size t num 待排序的元素个数 size t width 一个元素的大小
  • Java最小栈

    Java中的栈是一种后进先出 Last In First Out LIFO 的数据结构 它具有如下几个基本操作 将一个元素压入栈中 称为 入栈 push 将栈顶元素弹出 称为 出栈 pop 查看栈顶元素 称为 查看栈顶元素 peek 判断栈
  • Jenkins详细教程

    最近接触了jenkins这个东西 所以花点时间了解了下 它可以在代码上传仓库 如github gitee gitlab 后 在jenkins 一个网站界面 中通过获取代码仓库中最新代码 进行自动化部署 而省去手动打包 上传服务器 部署这一系
  • 多线程实现TCP的服务器,客户端随时收发信息

    1 作业需求 完成TCP的服务器 客户端 上交 服务器 客户端需要做到随时收发 多进程多线程实现 2 实现过程 1 服务器代码 include
  • 手机影像ISP流程:AWB(1)

    目录 AWB简介 AWB算法 灰度世界算法 灰度世界法的代码 灰度世界法的优点 灰度世界法的缺点 镜面反射法 镜面反射法的代码 镜面反射法的优点 镜面反射法的缺点 总结 AWB简介 AWB是Automatic white balance的英
  • 微信小程序中wx:for语法及其使用

    微信小程序官方文档中wx for 是用来做列表渲染 在组件上使用wx for绑定一个数组 数组的下标变量名默认为index 数组当前项的变量名默认为item 在Wxml中可以使用for循环获取数组数据 记住for循环后面跟的是数组 当然数组
  • 语义分割的常用指标详解

    1 混淆矩阵 假设有6个类别 L为10个真实标签的取值 P为对应的预测的标签值 先计算对应的n 类别数 这里假设为6 xL P bin的值一定是分类数的平方 混淆矩阵先将真实标签和预测标签抻成一维向量 做一个对应关系 nxL P 再将这个对
  • Linux必杀(十六):打包命令tar

    题记 tar 的参数非常多 挑重点的讲几个 tar j z cv f 新建的文件名 filename lt 打包与压缩 tar j z tv f 新建的文件名 lt 查看文件名 tar j z xv f 新建的文件名 c目录 lt 解压缩
  • JPA @MappedSuperclass注解的使用说明

    原文地址 http hi baidu com danghj item 507d7bdcee3ac052d63aaedf 基于代码复用和模型分离的思想 在项目开发中使用JPA的 MappedSuperclass注解将实体类的多个属性分别封装到
  • win7安装linux子系统,win7系统安装Version 1607中的Linux子系统的方法介绍

    win7系统使用久了 好多网友反馈说win7系统安装Version 1607中的Linux子系统的问题 非常不方便 有什么办法可以永久解决win7系统安装Version 1607中的Linux子系统的问题 面对win7系统安装Version
  • 大数据:Storm集成Kafka

    一 整合说明 Storm 官方对 Kafka 的整合分为两个版本 官方说明文档分别如下 Storm Kafka Integration 主要是针对 0 8 x 版本的 Kafka 提供整合支持 Storm Kafka Integration
  • WinDump使用资料

    WinDump使用提示 基本用法 windump aBdDeflnNOpqRStvxX c count F file i interface m module r file s snaplen T type w file E algo se
  • 【OpenGL】Shader实例分析(九)- AngryBots中的主角受伤特效

    AngryBots是Unity官方的一个非常棒的例子 很有研究价值 以前研究的时候 由于其内容丰富 一时间不知道从哪入手写文章分析 这一段时间研究shader技术比较多一些 就从shader的这一方面开始吧 首先分析其中的一个屏幕特效 当主
  • aab 安装报so找不到的问题(apk正常)

    最近搞海外 发现同样的源码打成apk是正常的 但是打成aab却崩溃 老是报找不到so 但是so又存在aab当中 花费了一些时间 下边是解决思路 首先 我发现不同系统的手机报错的信息不一样 这个让人很蛋疼 于是我从能解决的报错信息开始 最开始
  • js中[]、{}、()区别

    一 大括号 表示定义一个对象 大部分情况下要有成对的属性和值 或是函数体 表示对象 表示对象的属性 方法 如果用在方法名后面 代表调用 如 var LangShen Name Langshen AGE 28 上面声明了一个名为 LangSh
  • 卷积产生的参数量(Parameters)和计算量(FLOPs)

    输入 H in W in C in Filter k k 输出 H out W out C out 进行一次卷积操作产生的参数量 Parameters 和计算量 FLOPs 为 Parameters k k C in C out FLOPs
  • C++STL详解(十) -- 使用哈希表封装unordered_set和unordered_map

    文章目录 哈希表模板参数改造 针对模板参数V改造 增加仿函数获取具体数据类型 哈希表的正向迭代器 正向迭代器中的内置成员 正向迭代器的成员函数 哈希表插入函数的修改 适用于unordered map 一个类型K去做set和unordered