Java数据结构-哈希表讲解(Hash)

哈希表是我们经常频繁使用的数据结构,所以它的知识点比较重要,如HashMap啊,就是哈希表结构,哈希表的底层是数组+链表结构的,非常之聪明,两者优点结合,数组查询快,链表增删快,并且hash采用算法分析定位地址,而不用再像数组一样需要遍历。

哈希表为什么采用数组+链表的结构呢?

答:通过一定算法计算出来的数字就可以对应数组下标找到对应位置,假如现在计算完的数字是0需要定位到0坐标上,然后又有一个数据需要存储,计算完之后位置还是0,那现在怎么办呢,那也不能覆盖把,这种问题的出现也叫哈希碰撞,那现在解决办法就是加链表,当发现计算完的位置是一样的就存储在一连串的链表里就能实现。

我就不瞎巴巴了,还是上官方介绍哈希表的介绍把

1.哈希表的介绍

哈希表(hash table,也叫散列表)是根据关键码值(key,value)而直接进行访问的数据结构。也就是说它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度,这个映射函数叫做散列函数,存放记录的数组叫做散列表。

 

哈希表可以提供快速的插入和查找工作,哈希表运算的非常快,而且编程实现也比较容易。哈希表是数组和链表结构

记录的存储位置=f(关键字),这里的对应关系f称为散列函数,又称为哈希(哈希函数),采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表或哈希表

--视频资料介绍图片

2.哈希冲突

由于存储空间有限,hash计算以后可能不同的关键字映射到同一个哈希地址上,这种现象称之为哈希冲突,比如h(16)=16%11=5,h(27)=27%11=5,两个哈希地址就冲突了

--视频资料介绍图片

hash冲突是不可避免的,要解决hash冲突需要以下几种方式

3.开放地址法

3.1 线性探查(Linear Probing)

哈希冲突解决策略:开放寻址法(将所有的元素都存放在哈希表内的数组中,不使用额外的数据结构)

开放寻址法最简单的一种实现就是线性探查,步骤如下:

  1. 当插入新的元素时,使用哈希函数在hash表中定位元素位置;
  2. 检查哈希表中该位置是否已经存在元素,如果该位置为空,则插入并返回,否则转向步骤3
  3. 如果该位置为i,则检查i+1是否为空,如果已被占用,则检查i+2,以此类推,直到找到一个内容为空的位置。

视频资料图

线性探查(Linear Probing)方式虽然简单,但并不是解决冲突的最好的策略,因为会导致同类hash的聚集,这导致搜索hash表时冲突依然存在,例如上图例子中的哈希表,如果我们要访问Edward的信息,因为Edward的社保号是111-00-1235 哈希为1235,然而我们在1235找到的位置是Bob,所以再搜索1236,找到的确是Danny,以此类推才能找到最终答案,所以在查找的时候也会出现冲突的。

开放地址法的线性探测缺点就是,数据项聚集,越来越大,越到后面找到的空位插入数据项需要时间越多。也就是说当哈希表插入的数据越来越多的时候,探测的长度就非常长,后续的插入操作也非常耗时。

3.1.2 线性探测的操作流程

线性探测就是使用算术取余的方法计算余数,当产生冲突时就通过线性递增的方法进行探测,一直到数组的位置为空,插入数据项即可。

网图

以上是通过一个长度是10的数组进行hash存储。对数据10 20 30 23 24 25 35 37 38 9进行储存,数组长度为10;那么当第一个10%10=0,即把10放入HashArray[0]位置上,当对下一个数字20进行存储时,计算20%10=0,此时HashArray[0]已经有数据了,其实通过线性探测对HashArray[1]进行探测。此时HashArray[1]null,将数据20插入该位置,其他数据插入同理。

你已经猜到了,一个知识点第一个讲到的总是有缺陷,需要弥补的,那么二次探查和二度哈希出现在江湖。

3.2 二次探查(Quadratic Probing)

一种改进的方式为二次探查(Quadratic Probing),即每次检查位置空间的步长为平方倍数,也就是说,如果位置s被占用,则首先检查s+1的平方,然后检查s-1的平方,s+2的平方,s-2的平方,依次类推而不是像线性探查那样s+1,s+2.....方式增长,尽管如此,二次探查同样也会导致同类哈希聚集问题。

3.3 二度哈希(Rehashing)/双重哈希(Double Hashing)

二度哈希是把关键字用不同的的哈希函数再做一遍哈希化,用这个结果作为步长,对指定的关键字,探测的步长是不变的,可以说不同的关键字可以使用不同的步长,并且步长可以控制。一般来说,再哈希函数可以采用以下公式

stepSize=constant-(key%constant);

以上是使用地址开放的策略以及关于地址开放的三种探测方法,探测序列通常使用再哈希法生成。

4.哈希表链地址法

前面我们谈到了散列冲突处理的开放地址法,它的思路就是一旦发生了冲突,就去寻找下一个空的散列地址,那么,为什么有冲突就非要换地方呢,我们就在原地处理行不行呢?可以这样处理,这样才有的链地址法。

视频学习拉链法图

网图

将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针。

此时,已经不存在什么冲突换址的问题,无论有多少个冲突,都只是在当前位置给单链表增加结点的问题。很不错的解决思路吧?

4.1 拉链法的缺点与优点?

与开放地址法相比,拉链法有如下几个优点!

  • 拉链法处理冲突简单且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短
  • 由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况

拉链法的缺点:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指针空间用来扩大散列表的规模,可使装填因子变小,这又减少了开放定址法中的冲突,从而提高平均查找速度

 总结:链地址法的优势是对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了査找时需要遍历单链表的性能损耗,不过性能损耗在很多场合下也不是什么大问题。

5.哈希表的三种哈希算法

1.除法哈希法(The Division Method)

一种好的哈希做法是以独立于数据中可能存在的任何模式的方式导出哈希值。例如:除法哈希法用一个特定的质数来除所给的关键字,所得的余数即为该关键字的哈希值,除法哈希函数可表示为:

hash(key)=key mod m

其中key表示被哈希的关键字,m表示哈希表的大小,mod取余操作。假定所选择的质数与关键字分布中的任何模式都是无关的,这种方法常常可以给出很好的结果。

2.乘法哈希表(the multiplication method)

乘法哈希函数可表示为:Hash(key)=floor(m*(A* key mod 1)),其中floor 表示对表达式进行下取整,常数A取值范围为(0<A<1),m表示哈希表的大小mod为取余数操作。A*key mod 1 表示将key乘上某个在0~1之间的数并取乘积的小数部分,该表达式等价于[A*key-floor(A*key)]

乘法哈希法的一个优点是对m的选择没有什么特别的要求,一般选择它为2的某个幂次,这是因为我们可以在大多数计算机上更方便的实现该哈希函数。虽然这个方法对于任何A值都适用,但对某些值效果更好,最佳的选择与待哈希的数据的特征有关。

3.全域哈希法(Universal Hashing)

在向哈希表中插入元素时,如果所有的元素全部被哈希到同一个桶中,此时数据的存储实际上就是一个链表,那么平均的查找时间为O(n)。而实际上,任何一个特定的哈希函数都有可能出现这种最坏情况,唯一有效的改进方法就是随机地选择哈希函数,使之独立于要存储的元素。这种方法称作为全域哈希,全域哈希的基本思想是在执行开始时,从一组哈希函数中,随机得抽取一个作为要使用得哈希函数。就像快速排序中一样,随机化保证了没有拿一种输入会始终导致最坏情况的发生。同时,随机化也使得即使时对同一个输入,算法在每一次执行时的情况也都不一样。这样就确保了对于任何输入,算法都具有较好的平均运行情况。

Hasha,b(key)=((a*key+b)mod p) mod m

其中,p为一个足够大的质数,使得每一个可能的关键字key都落在0到p-1的范围内。m为哈希表中槽位数。

6.哈希表的应用与优缺点

6.1 哈希表的应用

Hash主要用于信息安全领域中加密算法,它把一些不同长度的信息转化成杂乱的128位的编码,这些编码值叫做hash值,也可以说,hash就是找到一种数据内容和数据存放地址之间的映射关系。

查找:哈希表,又称为散列,是一种更加快捷的查找技术。我们之前的查找,都是这样一种思路:集合中拿出来一个元素,看看是否与我们要找的相等,如果不等,缩小范围,继续查找。而哈希表是完全另外一种思路:当我知道key值以后,我就可以直接计算出这个元素在集合中的位置,根本不需要一次又一次的查找!

6.2 哈希表的优点

不论哈希表中有多少数据,查找,插入,删除,只需要接近常量的时间即0(1)的时间级。实际上,这只需要几条机器指令。

6.3 哈希表的缺点

它是基于数组的(linkedHashMap除外),数组创建后难于扩展,某些哈希表被基于填满时,性能下降的非常严重。

以上就是今天的全部内容!希望可以帮助每天都想要有改变的你和想要进步的你!

猜你喜欢

转载自blog.csdn.net/dfBeautifulLive/article/details/104898886