【C++自学笔记】哈希结构浅识(如何解决哈希冲突—闭散列、开散列)待更新~

一、哈希概念

顺序结构以及平衡树中,元素的关键码与其存储位置之间没有对应关系,因此在查找一个元素的时候,必须要经过关键码的多次比较。顺序查找时间复杂度为O(N),平衡树中为数的高度,即O(log2N),搜索的效率取决于搜索过程中元素的比较次数;

理想的搜索方法:可以不经过任何比较,一次直接从表中得到要搜索的元素。如果构造一种存储结构,通过某种函数(hashFunc)使元素的存储位置与它的关键码之间能够建立一一映射的关系,那么在查找时通过该函数可以很快找到该元素。

当向该结构中:

插入元素

  • 根据待插入元素的关键码,以此函数计算出该元素的存储位置并按此位置进行存放;

搜索元素

  • 对元素的关键码进行同样的计算,把求得的函数值当做元素的存储位置,在结构中按此位置取元素比较,若关键码相等,则搜索成功;

这种方式即为哈希(散列)方法,哈希方法中是哟个的转换函数称为哈希(散列)函数,构造出来的结构称为哈希表(Hast Table)

例如:数据集合{ 1, 7, 6, 4, 5, 9};

哈希函数设置为:hash(key) = key % capacity;capacity 为存储元素底层空间的总大小;

用该方法进行搜索不必进行多次关键的比较,依次搜索的速度比较快。

二、常见的哈希函数

常见的哈希函数:
1、直接定制法(常用)

取关键字的某个线性函数为散列地址:Hash(key) = A*key + B,优点:简单、均匀;缺点:需要事先直到关键字的分布情况;

使用场景:适合查找比较小且连续的数;

2、除留余数法(常用)

设散列表中允许的地址数为 m ,取一个不大于 m,但是最接近或者等于 m的质数 p作为除数;

按照哈希函数:Hash(key) = key % p(p <= m),将关键码转换成哈希地址;

3、平方取中法

将关键字平方后,取中间的值作为哈希地址;

该方法比较适用于不知道关键字的分布,而位数又不是很大的情况;

4、折叠法

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

折叠法适合事先不需要知道关键字的分布,适合关键字比价多的情况

5、随机数法

选择一个随机函数,取关键字的随机函数值为它的哈希地址,及H(key) = random(key),其中random为随机数函数;

通常适用于关键字长度不等时;

注意:哈希函数设计的越精妙,产生哈希冲突的可能性就越低,但是无法避免!

三、哈希冲突及其解决方式

1、哈希冲突的概念

对于两个数据元素的关键字 ki 和 ki != kj,但有 Hash(ki) == Has(kj),即:不同关键字通过相同的哈希函数计算出相同的哈希地址,该种现象称为哈希冲突或者哈希碰撞。

把具有不同关键码而具有相同哈希地址的数据元素称为“同义词”。

产生哈希冲突的一个原因可能是:哈希函数设计不够合理。

哈希函数的设计原则:

  • 哈希函数的定义域必须包括需要存储的全部关键码,而如果散列表允许有m个地址时,其值域必须在0到m-1之间;
  • 哈希函数计算出来的地址能均有分布在整个空间中;
  • 哈希函数应该比较简单;

2、哈希冲突的解决方式

解决哈希冲突的两种常见方式是:闭散列 和 开散列

1、闭散列

闭散列:也叫开放定址法,当发生哈希冲突的时候,如果哈希表未被装满,说明在哈希表中必然还有空位,那么可以把key 存到冲突位置中的“下一个”空位置中去。

如何寻找空位置:

1、线性探测

线性探测:从发生冲突的位置开始,依次向后探测,知道寻找到下一个空位置为止。

插入:

  • 通过哈希函数获取带插入元素在哈希表中的位置;
  • 如果找到该位置中没有元素则直接插入新元素,如果该位置中有元素发生哈希冲突,使用线性探测找到下一个空位置,插入新元素。

                              

删除:

  • 采用闭散列处理哈希冲突时,不能随便物理删除哈希表中已有的元素,若直接删除元素会影响其他元素的搜索。因此用线性探测采用标记的伪删除发来删除一个元素。
//哈希表每个空间都给个标记
//EMPTY此位置空,EXIST此位置已有元素,DELETE元素已经删除
enum State{ EMPTY, EXIST, DELETE};

线性探测的实现

线性探测的优缺点

  • 优点:实现非常简单
  • 缺点:一旦发生哈希冲突,所有的冲突连在一起,容易产生数据“堆积”,即:不同关键码占据了可利用的空位置,使得寻找某关键码的位置需要许多次比较,导致搜索效率降低,解决办法->二次探测;

2、二次探测

线性探测的缺陷时产生冲突的数据堆积在一起,这与其找下一个空位置有关系,因为找空位置的方式就是挨着往后找,因此二次探测为了避免这个问题: 找下一个空位值的方法为: 或者 。(其中:i = 1,2,3……),H0时通过散列函数Hash(X)对元素的关键码 key 进行计算得到的位置,m 时表的大小。

                       

当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不会被探测两次。因此只要表中有一半的空位置,就不会在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a 不超过0.5,如果超出就必须要考虑扩容。

因此:闭散列最大的缺陷就是空间利用率比较低,这也是哈希的缺陷;

2、开散列

1、开散列概念

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

                                              

从上图可以看出,开散列中每个桶中放的都是发生哈希冲突的元素;

2、开散列的实现

四、哈希的应用

发布了79 篇原创文章 · 获赞 28 · 访问量 7756

猜你喜欢

转载自blog.csdn.net/weixin_43753894/article/details/100056552