【数据结构】—— 哈希表之开散列解决哈希冲突(哈希桶)

哈希的概念 : https://blog.youkuaiyun.com/chenxiyuehh/article/details/90043229

开散列

1. 开散列概念

开散列法又叫链地址法(开链法)/哈希桶/拉链法,首先对关键码集合用散列函数计算散列地址,具有相同地址的关键码归于同一子集合,每一个子集合称为一个桶,各个桶中的元素通过一个单链表链接起来,各链表的头结点存储在哈希表中。

开散列哈希桶

2.开散列的实现

存储的节点定义

  • 我们知道开散列的实现其实是一个vector数组,存储的是节点的指针,相当一个指针数组,节点存储的东西应该有两个,一个是节点的值,一个是下一个节点的指针。
  • 因为我们要用哈希桶封装unordered_map/set,因此定义节点类型时要注意不能直接给value或是给键值对pair<K,V>,必须给一个模板参数,若是unordered_map调用就存储键值对pair<K,V>,unordered_set调用就存储value。
template<class V>
struct HashNode
{
	//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair<K,V>也可能存value
	V _valuefiled;
	HashNode<V>* _next;//哈希桶结构是一个vector数组下挂着链表,定义一个指针指向下一个节点的位置

	HashNode(const V& v)
		:_valuefiled(v)
		, _next(nullptr)
	{}
};

插入元素

  • 插入元素的思想很简单,首先计算元素在表中对应的位置,由于产生哈希冲突的序列不要求有序,找到位置之后可直接进行头插。

对于查找元素和删除元素这里就不赘述了,源码如下:

3.开散列实现完整代码
#pragma once

//开散列解决哈希冲突,哈希桶、拉链法
#include <iostream>
#include <vector>
using namespace std;

template<class V>
struct HashNode
{
	//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair<K,V>也可能存value
	V _valuefiled;
	HashNode<V>* _next;//哈希桶结构是一个vector数组下挂着链表,定义一个指针指向下一个节点的位置

	HashNode(const V& v)
		:_valuefiled(v)
		, _next(nullptr)
	{}
};

//由于之后要用哈希桶封装unordered_map/set,因此这里可能存pair<K,V>也可能存value
//由于pair<K,V>存储比较必须取key才能比较,因此传仿函数取key
template<class K,class V,class KeyOfValue>
class HashTable
{
	typedef HashNode<V> Node;
	typedef HashTable<K, V, KeyOfValue> HashTable;
public:
	HashTable()
		:_size(0)
	{}

	bool insert(const V& v)
	{
		//考虑增容
		CheckCapacity();

		KeyOfValue kov;
		const K& key = kov(v);
		size_t index = key % _table.size();

		Node* cur = _table[index];
		while (cur)
		{
			if (kov(cur->_valuefiled) == key)
				return false;
			cur = cur->_next;
		}
		//走到这说明没有相同的元素,可以进行插入,
		//由于哈希桶不规定产生冲突的序列有序,可以进行头插比较简单
		Node* newnode = new Node(v);
		newnode->_next = _table[index];
		_table[index] = newnode;

		++_size;
		return true;
	}

	void CheckCapacity()
	{
		//当负载因子==1时扩容
		if (_table.size() == _size)
		{
			size_t newsize = _table.size() == 0 ? 10 : _table.size() * 2;
			//这里不像之前开散列一样创建一个新的哈希表
			//再调用Insert的原因是旧表的节点可以直接拿到新的vector数组中进行插入
			vector<Node*> newtable;
			newtable.resize(newsize);

			//遍历旧表,在新的vector数组中找到对应位置,将旧表节点插入
			for (size_t i = 0; i < _table.size(); ++i)
			{
				Node* cur = _table[index];
				//将节点从旧表中拆出来,再重新计算节点在新表中的位置进行插入
				while (cur)
				{
					Node* next = cur->_next;
					size_t index = KeyOfValue()(cur->_valuefiled) % newsize;

					//头插入新表中
					cur->_next = newtable[index];
					newtable[index] = cur;

					cur = next;
				}
				//将原来的表置空
				_table[i] = nullptr;
			}
			//交换新旧两标的资源,出作用域后新表自动调用析构函数释放旧表资源
			_table.swap(newtable);
		}
	}

	Node* Find(const K& key)
	{
		size_t index = key % _table.size();
		Node* cur = _table[index];
		while (cur)
		{
			if (KeyOfValue()(cur->_valuefiled) == key)
				return cur;

			cur = cur->_next;
		}
		return nullptr;
	}

	bool Erase(const K& key)
	{
		size_t index = key % _table.size();
		Node* cur = _table[index];
		Node* prev = nullptr;

		while (cur)
		{
			if (KeyOfValue()(cur->_valuefiled) == key)
			{
				if (prev == nullptr)//头删
					_table[index] = cur->_next;
				else
					prev->_next = cur->_next;

				delete cur;
				--_size;
				return true;
			}
		}
		return false;
	}
private:
	vector<Node*> _table;//vector中的size为哈希表的大小
	size_t _size;//哈希表中存储的有效元素的个数
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值