【C++】STL详解(十二)—— 用哈希表封装出unordered_map和unordered_set

在这里插入图片描述

​📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:C++学习
🎯长路漫漫浩浩,万事皆有期待

上一篇博客:【C++】STL详解(十一)—— unordered_set、unordered_map的介绍及使用

哈希表源代码

下面我们将对一个KV模型的哈希表进行封装,同时模拟实现出C++STL库当中的unordered_map和unordered_set,所用到的哈希表源代码如下:

//每个哈希桶中存储数据的结构
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, class V>
class HashTable
{
   
	typedef HashNode<K, V> Node; //哈希结点类型
public:
	//获取本次增容后哈希表的大小
	size_t GetNextPrime(size_t prime)
	{
   
		const int PRIMECOUNT = 28;
		//素数序列
		const size_t primeList[PRIMECOUNT] =
		{
   
			53ul, 97ul, 193ul, 389ul, 769ul,
			1543ul, 3079ul, 6151ul, 12289ul, 24593ul,
			49157ul, 98317ul, 196613ul, 393241ul, 786433ul,
			1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul,
			50331653ul, 100663319ul, 201326611ul, 402653189ul, 805306457ul,
			1610612741ul, 3221225473ul, 4294967291ul
		};
		size_t i = 0;
		for (i = 0; i < PRIMECOUNT; i++)
		{
   
			if (primeList[i] > prime)
				return primeList[i];
		}
		return primeList[i];
	}
	//插入函数
	bool Insert(const pair<K, V>& kv)
	{
   
		//1、查看哈希表中是否存在该键值的键值对
		Node* ret = Find(kv.first);
		if (ret) //哈希表中已经存在该键值的键值对(不允许数据冗余)
		{
   
			return false; //插入失败
		}

		//2、判断是否需要调整哈希表的大小
		if (_n == _table.size()) //哈希表的大小为0,或负载因子超过1
		{
   
			//增容
			//a、创建一个新的哈希表,新哈希表的大小设置为原哈希表的2倍(若哈希表大小为0,则将哈希表的初始大小设置为10)
			vector<Node*> newtable;

			newtable.resize(GetNextPrime(_table.size()));
				
			//b、将原哈希表当中的结点插入到新哈希表
			for (size_t i = 0; i < _table.size(); i++)
			{
   
				if (_table[i]) //桶不为空
				{
   
					Node* cur = _table[i];
					while (cur) //将该桶的结点取完为止
					{
   
						Node* next = cur->_next; //记录cur的下一个结点
						size_t index = cur->_kv.first%newtable.size(); //通过哈希函数计算出对应的哈希桶编号index(除数不能是capacity)
						//将该结点头插到新哈希表中编号为index的哈希桶中
						cur->_next = newtable[index];
						newtable[index] = cur;

						cur = next; //取原哈希表中该桶的下一个结点
					}
					_table[i] = nullptr; //该桶取完后将该桶置空
				}
			}
			//c、交换这两个哈希表
			_table.swap(newtable);
		}

		//3、将键值对插入哈希表
		size_t index = kv.first % _table.size(); //通过哈希函数计算出对应的哈希桶编号index(除数不能是capacity)
		Node* newnode = new Node(kv); //根据所给数据创建一个待插入结点
		//将该结点头插到新哈希表中编号为index的哈希桶中
		newnode->_next = _table[index];
		_table[index] = newnode;

		//4、哈希表中的有效元素个数加一
		_n++;
		return true;
	}
	//查找函数
	HashNode<K, V>* Find(const K& key)
	{
   
		if (_table.size() == 0) //哈希表大小为0,查找失败
		{
   
			return nullptr;
		}

		size_t index = key % _table.size(); //通过哈希函数计算出对应的哈希桶编号index(除数不能是capacity)
		//遍历编号为index的哈希桶
		HashNode<K, V>* cur = _table[index];
		while (cur) //直到将该桶遍历完为止
		{
   
			if (cur->_kv.first == key) //key值匹配,则查找成功
			{
   
				return cur;
			}
			cur = cur->_next;
		}
		return nullptr; //直到该桶全部遍历完毕还没有找到目标元素,查找失败
	}
	//删除函数
	bool Erase(const K& key)
	{
   
		//1、通过哈希函数计算出对应的哈希桶编号index(除数不能是capacity)
		size_t index = key % _table.size();
		//2、在编号为index的哈希桶中寻找待删除结点
		Node* prev = nullptr;
		Node* cur = _table[index];
		while (cur) //直到将该桶遍历完为止
		{
   
			if (cur->_kv.first == key) //key值匹配,则查找成功
			{
   
				//3、若找到了待删除结点,则删除该结点
				if (prev == nullptr) //待删除结点是哈希桶中的第一个结点
				{
   
					_table[index] = cur->_next; //将第一个结点从该哈希桶中移除
				}
				else //待删除结点不是哈希桶的第一个结点
				{
   
					prev->_next = cur->_next; //将该结点从哈希桶中移除
				}
				delete cur; //释放该结点
				//4、删除结点后,将哈希表中的有效元素个数减一
				_n--;
				return true; //删除成功
			}
			prev = cur;
			cur = cur->_next;
		}
		//假删除可能会导致迭代器失效
			
		return false; //直到该桶全部遍历完毕还没有找到待删除元素,删除失败
	}
private:
	vector<Node*> _table; //哈希表
	size_t _n = 0; //哈希表中的有效元素个数
};

哈希表模板参数的控制

首先需要明确的是,unordered_set是K模型的容器,而unordered_map是KV模型的容器。

要想只用一份哈希表代码同时封装出K模型和KV模型的容器,我们必定要对哈希表的模板参数进行控制。

为了与原哈希表的模板参数进行区分,这里将哈希表的第二个模板参数的名字改为T。

template<class K, class T>
class HashTable

如果上层使用的是unordered_set容器,那么传入哈希表的模板参数就是key和key。

template<class K>
class unordered_set
{
   
public:
	//...
private:
	HashTable<K, K> _ht; //传入底层哈希表的是K和K
};

但如果上层使用的是unordered_map容器,那么传入哈希表的模板参数就是key以及key和value构成的键值对。

template<class K, class V>
class unordered_map
{
   
public:
	//...
private:
	HashTable<K, pair<K, V>> _ht; //传入底层哈希表的是K以及K和V构成的键值对
};

也就是说,哈希表中的模板参数T的类型到底是什么,完全却决于上层所使用容器的种类。

而哈希结点的模板参数也应该由原来的K、V变为T:

上层容器是unordered_set时,传入的T是键值,哈希结点中存储的就是键值。
上层容器是unordered_map时,传入的T是键值对,哈希结点中存储的就是键值对。

更改模板参数后,哈希结点的定义如下:

//哈希结点的定义
template<class T>
struct HashNode
{
   
	T _data;
	HashNode<T>* _next;

	//构造函数
	HashNode(const T& data)
		:_data(data)
		, _next(nullptr)
	
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sherry的成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值