unordered系列关联式容器底层哈希结构的介绍,哈希表的模拟实现(哈希冲突的解决方法采用闭散列线性探测)

目录

前言

unordered系列关联式容器之所以处理数据的效率比较高,是因为底层使用了哈希结构,哈希结构的优点是:不经过任何比较,一次直接从表中得到要搜索的元素,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

一、哈希的概念

顺序结构以及平衡树 中,元素关键码与其存储位置之间没有对应的关系,因此在 查找一个元素
时,必须要经过关键码的多次比较 搜索的效率取决于搜索过程中元素的比较次数。
理想的搜索方法: 可以 不经过任何比较,一次直接从表中得到要搜索的元素 如果构造一种存储结构,通过某种函数 (hashFunc) 使元素的存储位置与它的关键码之间能够建立 一一映射的关系,那么在查找时通过该函数可以很快找到该元素
向结构中插入元素:根据待插入元素的关键码,用hashFunc函数(通常是用除留余数法)算出该元素的存储位置并按此位置进行存放。
从结构中搜索元素:对元素的关键码进行同样的计算,把求得的函数值当作元素的存储位置,在结构中按此位置取元素比较,若关键码相等,责搜索成功。
该方法即为哈希(散列)方法,哈希方法中使用的转换函数称为哈希(散列)函数,构造出来的结构称
为哈希表(HashTable)或者叫作散列表。
例如:数据集合{176459}
哈希函数设置为: hash(key) = key % capacity ;(capacity为存储元素底层空间总的大小。)
用该方法进行搜索不必进行多次关键码的比较,因此搜索的速度比较快。 但是如果按照上述哈希方
式,向集合中插入元素55,就会发生哈希冲突。

二、哈希冲突以及解决冲突的方法(闭散列线性探测)

不同关键码通过相同哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或哈希碰撞
通常把具有不同关键码而具有相同哈希地址的数据元素称为 同义词
那么该如何解决这种冲突呢?
闭散列:也叫开放定址法,当发生哈希冲突时,如果哈希表未被装满,说明在哈希表中必然还有
空位置,那么可以把 key 存放到冲突位置中的 下一个 空位置中去。(“下一个”不一定只是隔了一个位置,因为闭散列中有两种方法:线性探测和二次探测。线性探测则是下一个,二次探测则不一定是下一个位置)。
下面我们介绍一下解决哈希冲突的一个方法闭散列线性探测。
线性探测:从发生冲突的位置开始,依次向后探测,直到寻找到下一个空位置为止
插入数据:
通过哈希函数获取待插入元素在哈希表中的位置。
如果该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突, 使用线性探测找到 下一个空位置,插入新元素 。
删除元素:
采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素
会影响其他元素的搜索 。比如删除元素5 ,如果直接删除掉, 55查找起来可能会受影 响。因此 线性探测采用标记的伪删除法来删除一个元素

三、哈希表的模拟实现(哈希冲突的解决方法采用闭散列线性探测)

#include<vector>
#include<iostream>
using namespace std;

namespace keke
{
	enum Status
	{
		EMPTY,
		EXIST,
		DELETE
	};

	template<class K,class V>
	struct HashData
	{
		pair<K, V> _kv;
		Status _s;
	};

	//仿函数
	/*template<class K>
	class HashFunc
	{
	public:
		size_t operator()(const K& key)
		{
			size_t i = 0;
			for (auto e : key)
			{
				i += e;
			}
			return i % _tables.size();
		}
	};*/

	template<class K, class V>
	class HashTable
	{
	public:
		HashTable()
		{
			_tables.resize(10);
		}

		bool Insert(const pair<K, V>& kv)
		{
			//负载因子:已经存储数据个数/_tables大小
			if (Find(kv.first))
				return false;
			//扩容
			if ((double)_n / _tables.size() >= 0.7)
			{
				size_t newSize = _tables.size() * 2;
				HashTable<K, V> newHashTable;//定义一个新的哈希表,为什么不将原来的哈希表扩容呢?这是
	            //因为如果扩容的话就会影响查找数据,比如:当原哈希表的空间大小为5在下标为3的位置插入关键码3时再插入13
				//就要根据线性探测的方法将关键码13存入到关键码3的下一个位置处,但是如果原地2倍扩容后,在进行Find时关键码3正常
				//但是当Find关键码13时就会出现问题

				newHashTable._tables.resize(newSize);
				for (size_t i = 0;i < _tables.size();++i)//遍历旧的哈希表
				{
					if (EXIST == _tables[i]._s)
					{
						newHashTable.Insert(_tables[i]._kv.first);
					}
				}
				
				//将临时创建的扩容后的哈希表里的_tables与旧哈希表里的_tables互换
				_tables.swap(newHashTable._tables);
			}
			size_t hashi = kv.first % _tables.size();
			while (_tables[hashi]._s != EXIST)
			{
				hashi++;
				hashi %= _tables.size();//当hashi的值等于_tables.size()的值则将hashi置为0
			}
			_tables[hashi]._kv = kv;
			_tables[hashi]._s = EXIST;
			++_n;
			return true;
		}
		HashTable<K, V>* Find(const K& key)
		{
			size_t hashi = key % _tables.size();
			while (_tables[hashi]._s != EMPTY)
			{
				if (_tables[hashi]._s == EXIST && _tables[hashi]._kv.first == key)
				{
					return &_tables[hashi];
				}
				hashi++;
				hashi %= _tables.size();
			}
			return nullptr;
		}
		bool Erase(const K& key)
		{
			HashData<K, V>* ret = Find(key);
			if (ret)
			{
				ret->_s = DELETE;
				--_n;
				return true;
			}
			return false;
		}
		void Print()
		{
			for (size_t i = 0;i < _tables.size();++i)
			{
				if (EXIST == _tables[i]._s)
					printf("[&d]->%d\n", i, _tables[i]._kv.first);
				else if (EMPTY == _tables[i]._s)
					printf("[&d]->E\n", i);
				else
					printf("[&d]->D\n", i);
			}
		}
	private:
		vector<HashData<K, V>> _tables;
		size_t _n = 0;
	};
}
//范围小的向范围大的提升,有符号的向无符号的的提升

 

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值