哈希-闭散列

哈希的概念

顺序结构以及平衡树中,元素关键字与其储存位置并没有对应的关系,因此在查找一个元素的时候,即使是平衡树中也需要经过多次比较。顺序查找的时间复杂度为O(N),平衡树查找一个元素的时间复杂度为O(log2N),N为树的高度。那么有没有更快的方式,甚至让时间复杂度可以达到常数级别的呢?
因此,理想的搜索方法是可以不进行任何比较,一次直接从表中得到想要搜索的元素。通过某种函数使得元素的存储位置和他的关键字之间能够建立起一一映射的关系,那么在查找时通过该函数可以很快找到该元素。于是便有了哈希这个存储数据的方法。哈希方法中使用的转换函数被称为哈希函数,构造出来的结构被称为哈希表

哈希函数

如果把哈希函数设置为:hask(key) = key % capacity。这里的capacity是存储元素空间的总大小,如果用vector来进行存放,那么其实这里的capacity实际上是vector数组的size大小。用这个方法来存放如下数据集合:{1,3,6,4,5,2}
在这里插入图片描述
用该方法来存放数据,在搜索数据的时候不需要再顺序遍历或者进行关键字的比较,根据哈希函数便可以快速确定位置,因此搜索速度会比较快。

常见的哈希函数

直接定值法

取关键字的某个线性函数为散列地址:Hash(Key) = A*Key + B,这种哈希函数优点是简单均匀,缺点是需要事先知道关键字的分布情况

除留余数法

除留余数法是比较常用的方法。散列中存储的最大空间为m,取一个不大于m,但最接近或者等于m的数作为除数,按照哈希函数:Hash(key) = key% p(p<=m),将关键码转换成哈希地址

平法取中法

假设关键字为1234,对它平方就是1522756,抽取中间的3位227作为哈希地址; 再比如关键字为4321,对它平方就是18671041,抽取中间的3位671(或710)作为哈希地址
平方取中法比较适合:不知道关键字的分布,而位数又不是很大的情况

折叠法

折叠法是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。

哈希冲突

但是使用哈希来存放数据也不是没有缺点的,哈希函数为hask(key) = key % capacity这种情况下,如果存放数据集合{1,11,21,31,2,12,22}这样的情况,他们通过哈希函数算出来的结果出现很多重复,计算出了相同的哈希地址。这种现象就被称为哈希冲突或者哈希碰撞,那么该如何处理哈希冲突呢?
解决哈希冲突的两种常见的方法是:闭散列和开散列

闭散列

闭散列又叫做开放定址法。他的做法是当发生哈希冲突的时候,如果哈希表未被填满,说明哈希表中还有空位置,那么就可以把数据放到冲突位置的“下一个”空位置去。这个下一个空位置有两种选取方式。

线性探测

闭散列寻找下一个空位置,其中一种方法就是使用线性探测,线性探测原理很简单,从冲突位置开始,依次向后找,直到找到下一个空位置为止。
线性探测插入数据代码如下:这里是简单的线性探测插入代码,主要是体现了线性探测插入时,是从发生冲突位置开始依次向后寻找空位置的情况。但是实际插入的时候需要考虑是否存在重复以及是否需要扩容等问题,这些在后面的代码中会进行实现。

	bool Insert(const pair<K, V>& _kv)//插入数据
	{
   
		size_t start = _kv.first % _table.size();//计算起始位置
		size_t i = 0;
		size_t index = start + i;
		while (_table[index]._status == EXIST)
		{
   
			i++;
			index = start + i;
			index %= _table.size();
		}
		_table[index]._kv = kv;
		_table[index]._status = EXIST;
		_n++;
		return true;

	}

显然这样看起来线性探测存在不足的情况:如果一旦发生哈希冲突,并且所有的冲突连在一起,容易产生数据的堆积,不同的关键字占据了可利用的空位置导致其他原本处于该位置的代码需要经过多次比较重新找位置,效率会比较低。

二次探测

为了改善线性探测这个缺点,于是有了二次探测这种解决哈希冲突的方法。二次探测顾名思义就是使用2次方。与线性探测不同,他在寻找下一个空位置的时候,是通过该式子来进行的:Hash= (index +i^2 )% m,这样一来就不会出现连续的冲突的哈希位置,一定程度上避免了堆积的情况。他的代码实现如下:唯一的区别就是在于找哈希地址的时候,寻找的间隔变为了平方。

	bool Insert(const pair<K, V>& _kv)//插入数据
	{
   
		size_t start = _kv.first % _table.size();//计算起始位置
		size_t i = 0;
		size_t index = start + i;
		while (_table[index]._status == EXIST)
		{
   
			i++;
			index = start + (i * i);
			//index = start + i;
			index %= _table.size();
		}
		_table[index]._kv = kv;
		_table[index]._status = EXIST;
		_n++;
		return true;

	}

载荷因子

但是不管是连续探测还是二次探测,都存在一个问题。如果哈希表内存放的数据过多,那么他在寻找空位置的时候,消耗也就越大,甚至在二次探测的过程中可能出现死循环的情况。那么哈希表中出现了一个新的概念:载荷因子(α)又可以叫做负载因子。他被定义为哈希表中的元素个数/哈希表的总容量。α越大,填入表中的元素越多,产生冲突的可能性越大,α越小,表明填入表中的元素越少,产生冲突的可能性越小,效率越高,空间浪费越多。一般载荷因子需要控制在0.7-0.8以下。如果载荷因子超过了规定为大小,那么就要对哈希表进行扩容,扩容代码如下:

		if (_n * 10 / _table.size() >= 7)//如果载荷因子大于0.7就要进行扩容
		{
   
			size_t newCapacity = _table.size() == 0 ? 10 : _table
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值