C++数据结构:哈希桶 -- 通过开散列的方法解决哈希冲突

本文详细介绍了哈希桶的概念和实现,包括哈希表节点数据结构、查找特定Key值的find函数、哈希桶的扩容机制、数据插入insert操作、删除erase操作,以及析构函数。哈希桶使用单链表解决哈希冲突,允许负载因子大于1,并在需要时进行扩容。
摘要由CSDN通过智能技术生成

目录

一. 什么是哈希桶

二. 哈希桶的实现

2.1 哈希表节点数据

2.2 特定Key值的查找find

2.3 哈希桶的扩容

2.4 数据插入操作insert

2.5 数据删除操作erase

2.6 哈希桶的析构函数 

附录:哈希桶的实现完整版代码


一. 什么是哈希桶

之前的博客中我提到过,可以采用闭散列(二次探测、线性探测)的方法来解决哈希冲突,但是,无论是线性探测还是二次探测,其解决哈希冲突的本质方法都是去“占位”,即:发生哈希冲突的Key会去占用其他Key值原本应当占用的位置,这样一个冲突可能引发一串冲突。

为了解决闭散列方法的上述弊端,可以使用哈希桶(开散列)的方法来解决哈希冲突。在hashTable的每个位置都挂一个哈希桶,哈希桶用单链表来表示。将所有发生冲突的数据都挂在一个桶上。

此时,hashTable存储的数据类型时单链表节点,hashTable[i]是每个哈希桶的头结点,发生冲突时的数据插入相当于单链表的头插操作。

图1.1 哈希桶结构图

二. 哈希桶的实现

2.1 哈希表节点数据

由于哈希桶的本质是挂在哈希表上的单链表,因此,哈希表节点的数据应当为单链表节点,包括:

  • 一个键值对pair<K, V> _kv,记录key值和与之配对的value值。
  • 一个节点指针_next,指向单链表的下一个节点。

在Hash类模板中,将hashDate<K, V>类型重定义为Node。

代码2.1:(哈希表节点)

template<class K, class V>
struct hashDate
{
	std::pair<K, V> _kv;
	hashDate<K, V>* _next;

	hashDate(const std::pair<K, V>& kv)
		: _kv(kv)
		, _next(nullptr)
	{ }
};

2.2 特定Key值的查找find

函数Node* find(const K& key),找到了返回key所在的节点地址,找不到返回nullptr。

  1. 通过哈希函数,获取key所在的哈希桶应当挂在_hashTable哪个下标位置处(记为hashi)。
  2. 遍历以_hashTable[hashi]为头结点的单链表,查找key,找到了返回链表节点。
  3. 如果遍历到nullptr还没找到,则表示key不存在于哈希桶中,函数返回false。

代码2.2:(find函数的实现)

	Node* find(const K& key)
	{
		//哈希表中没有数据就直接返回false
		//这里是为了避免模0(%0)错误
		if (_hashTable.size() == 0)
		{
			return nullptr;
		}

		HashKey hashKey;
		//通过哈希函数计算key所在哈希桶
		size_t hashi = hashKey(key) % _hashTable.size();  
		//哈希桶单链表头结点
		Node* cur = _hashTable[hashi];

		//变量哈希桶(单链表),寻找key
		while (cur)
		{
			if (hashKey(cur->_kv.first) == hashKey(key))
			{
				return cur;
			}

			cur = cur->_next;
		}

		return nullptr;
	}

2.3 哈希桶的扩容

如果通过闭散列(线性探测或二次探测)的方法来解决哈希冲突,那么哈希表中存储的数据个数_size一定不能超过_table.size()。但是如果使用哈希桶,哈希表的每个位置挂的哈希桶可以有多个数据,因此负载因子是可以大于1的。

闭散列一般当负载因子大于0.7~0.8是扩容,而哈希桶则应适度提高。可以认为当插入数据后负载因子大于1就扩容。

哈希桶扩容同样需要改变原来的结构,因为不同的Key值对于的存储位置会发生改变。这里不再复用insert函数来实现数据位置的改变,而是依次遍历每个哈希桶的每个节点,创建一个新的vector作为hashTable,通过单链表头插,来使数据满足扩容后的哈希结构要求。

图2.3 扩容前后哈希桶结构的变化

代码2.3:(哈希表扩容) 

		//扩容(负载因子大于1)
		if (_hashTable.size() == 0 || _size == _hashTable.size())
		{
			size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();

			//将原来哈希表数据的位置进行相应改变
			//这里使用一个新的vector来实现
			std::vector<Node*> newTable;
			newTable.resize(newSize, nullptr);

			//将原来table每个位置处挂的单链表,挪到新的table对应位置
			for (size_t i = 0; i < _hashTable.size(); ++i)
			{
				Node* cur = _hashTable[i];

				while (cur)
				{
					Node* next = cur->_next;
					size_t newHashi = hashKey(cur->_kv.first) % newSize;

					//执行头插操作
					cur->_next = newTable[newHashi];
					newTable[newHashi] = cur;

					cur = next;
				}

				_hashTable[i] = nullptr;
			}

			_hashTable.swap(newTable);
		}

2.4 数据插入操作insert

哈希桶是挂在hashTable指定位置处的单链表。进行数据插入时,只需在检查数据是否已经存在、是否需要扩容之后,要通过哈希函数Hash(Key)找到对应的位置,然后执行单链表头插操作即可。

图2.4 数据插入操作

2.5 数据删除操作erase

通过哈希函数获得哈希桶的位置后,遍历单链表(哈希桶)找到key所在的位置,执行单链表节点删除操作即可。

代码2.5:(数据删除)

	bool erase(const K& key)
	{
		if (_hashTable.size() == 0)
		{
			return false;
		}

		HashKey hashKey;

		size_t hashi = hashKey(key) % _hashTable.size();
		Node* prev = nullptr;
		Node* cur = _hashTable[hashi];

		while (cur)
		{
			//找到key了,删除
			if (hashKey(cur->_kv.first) == key)
			{
				//头删
				if (prev == nullptr)
				{
					_hashTable[hashi] = cur->_next;
					free(cur);
					cur = nullptr;
				}
				else  //中间删或尾删
				{
					prev->_next = cur->_next;
					free(cur);
					cur = nullptr;
				}

				--_size;
				return true;
			}

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

		return false;
	}

2.6 哈希桶的析构函数 

自己编写代码,依次对每个哈希桶进行释放,每个哈希桶的析构等同于单链表的析构。由于vector为自定义类型数据,编译的会主动调用vector的析构函数释放hashTable的空间。

代码2.6:(析构函数)

	~Hash()   //析构函数
	{
		//释放每个节点上挂的数据
		for (size_t i = 0; i < _hashTable.size(); ++i)
		{
			Node* cur = _hashTable[i];

			//释放每个非空节点
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}

			_hashTable[i] = nullptr;
		}
	}

附录:哈希桶的实现完整版代码

#include<vector>

template<class K, class V>
struct hashDate
{
	std::pair<K, V> _kv;
	hashDate<K, V>* _next;

	hashDate(const std::pair<K, V>& kv)
		: _kv(kv)
		, _next(nullptr)
	{ }
};

template<class K>
struct HashKeyVal
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

template<class K, class V, class HashKey = HashKeyVal<K>>
class Hash
{
	typedef hashDate<K, V> Node;

public:
	~Hash()   //析构函数
	{
		//释放每个节点上挂的数据
		for (size_t i = 0; i < _hashTable.size(); ++i)
		{
			Node* cur = _hashTable[i];

			//释放每个非空节点
			while (cur)
			{
				Node* next = cur->_next;
				delete cur;
				cur = next;
			}

			_hashTable[i] = nullptr;
		}
	}

	bool insert(const std::pair<K, V>& kv)
	{
		//查找 -- 确保不插入重复数据
		if (find(kv.first))
		{
			return false;
		}

		HashKey hashKey;

		//扩容(负载因子大于1)
		if (_hashTable.size() == 0 || _size == _hashTable.size())
		{
			size_t newSize = _hashTable.size() == 0 ? 10 : 2 * _hashTable.size();

			//将原来哈希表数据的位置进行相应改变
			//这里使用一个新的vector来实现
			std::vector<Node*> newTable;
			newTable.resize(newSize, nullptr);

			//将原来table每个位置处挂的单链表,挪到新的table对应位置
			for (size_t i = 0; i < _hashTable.size(); ++i)
			{
				Node* cur = _hashTable[i];

				while (cur)
				{
					Node* next = cur->_next;
					size_t newHashi = hashKey(cur->_kv.first) % newSize;

					//执行头插操作
					cur->_next = newTable[newHashi];
					newTable[newHashi] = cur;

					cur = next;
				}

				_hashTable[i] = nullptr;
			}

			_hashTable.swap(newTable);
		}

		//获取插入位置
		size_t hashi = hashKey(kv.first) % _hashTable.size();
		Node* node = new Node(kv);

		//插入数据
		node->_next = _hashTable[hashi];
		_hashTable[hashi] = node;

		++_size;
	}

	bool erase(const K& key)
	{
		if (_hashTable.size() == 0)
		{
			return false;
		}

		HashKey hashKey;

		size_t hashi = hashKey(key) % _hashTable.size();
		Node* prev = nullptr;
		Node* cur = _hashTable[hashi];

		while (cur)
		{
			//找到key了,删除
			if (hashKey(cur->_kv.first) == key)
			{
				//头删
				if (prev == nullptr)
				{
					_hashTable[hashi] = cur->_next;
					free(cur);
					cur = nullptr;
				}
				else  //中间删或尾删
				{
					prev->_next = cur->_next;
					free(cur);
					cur = nullptr;
				}

				--_size;
				return true;
			}

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

		return false;
	}

	Node* find(const K& key)
	{
		//哈希表中没有数据就直接返回false
		//这里是为了避免模0(%0)错误
		if (_hashTable.size() == 0)
		{
			return nullptr;
		}

		HashKey hashKey;
		//通过哈希函数计算key所在哈希桶
		size_t hashi = hashKey(key) % _hashTable.size();  
		//哈希桶单链表头结点
		Node* cur = _hashTable[hashi];

		//变量哈希桶(单链表),寻找key
		while (cur)
		{
			if (hashKey(cur->_kv.first) == hashKey(key))
			{
				return cur;
			}

			cur = cur->_next;
		}

		return nullptr;
	}

private:
	size_t _size = 0;   //哈希表中数据个数
	std::vector<Node*> _hashTable;   //哈希表(存储桶节点)
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值