数据结构之Hash表——修改中

1、什么是哈希表?

        散列表(Hash table,也叫哈希表),是根据键(key)而直接访问在内存存储位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

        要知道数据结构有很多中,每一种都有各自的特点,那么哈希表既然也是一种数据结构,那它有什么特点呢?可以根据一个key值来直接访问数据,因此查找速度快。

        哈希表本质上是个数组(数组+链表,数组+红黑树,只能说它的底层实现是用到了数组,简单点说,在数组的这个基础上再捯饬捯饬,加工加工,变得更加有特色了,然后人家就自立门户,叫哈希表。// Hash表可以在常数时间内[O(1)]存储数据并进行搜索。

        哈希主要有三个组成部分:key(键)、Hash函数、hash表

        哈希表的基本思想是将关键字作为输入,通过哈希函数计算出一个哈希值,然后将该哈希值映射到一个存储桶中。存储桶通常是一个数组,每个数组元素称为一个哈希槽(slot),可以存储一个键值对。在哈希表中,同一哈希值的键值对可能会映射到同一个存储桶中,因此需要处理哈希冲突的情况。

        Java中实现哈希表两种的方法:

  1. 数组+链表(Java常用)
  2. 数组+二叉树(红黑树,当冲突达到一定数量级后,链表转为红黑树——Java改良)

        哈希表充分体现了算法设计领域的经典思想:空间换时间

2、什么是Hash函数?

        哈希函数(Hash function)是一种将输入数据映射为固定长度的哈希值(也称为哈希码)的函数。它接受任意长度的输入数据,通过一系列的计算操作,生成一个固定长度的输出。

        哈希函数的主要目标是将输入数据均匀地映射到哈希值空间中,以便尽量减少不同输入产生相同哈希值(哈希冲突)的可能性。一个好的哈希函数应具备以下特点:

  1. 一致性:对于相同的输入,哈希函数始终生成相同的哈希值。这意味着对于给定的输入,哈希函数应该产生可预测且确定性的输出。
  2. 唯一性:对于不同的输入,哈希函数应该生成不同的哈希值。这确保了不同的输入具有不同的标识符。
  3. 高效性:哈希函数的计算速度应该快,以便在实际应用中能够处理大量数据。
  4. 均匀性:好的哈希函数应该具有良好的分布特性,使得输入数据的微小变化能够显著改变生成的哈希值,以减少冲突的概率。// 减少Hash冲突

        为什么需要Hash函数?

        举个生活中的例子,比如,现在有个电话本,上面记录的有姓名和对应的手机号,如果想让你找王二的手机号是多少,那么你会怎么做呢?是直接挨个找王二吗?那么有没有想过,如果这个王二是在最后几页,那岂不是前面几页都白找了,有没有更快的方式呢?

        如果可以按照人名给分个类,比如按照首字母来排序,就 abcd 那样的顺序,这样根据王二就知道去找 w 这些,这样不就快很多了?所以,通过这种办法,你一下子就能找到王二,不用再挨个的去查找了。这里通过一些特定的方法去得到一个特定的值,比如这里取人名的首字母,那么如果是放到数学中,就是类似一个函数,给定一个值,经过某些加工得到另外一个值,就像这里给定的人名,经过些许加工拿到首字母,那么这个函数或者是这个方法在哈希表中就叫做散列函数,其中规定的一些操作就叫做函数法则。

        记录的存储位置 = F(关键字)

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

        常用的哈希函数设计方法——>取模(模数为素数)

        哈希函数的设计原则计算高效简便,“键”通过哈希函数得到的“索引”分布越均匀越好

(1)键值对和Entry

哈希表本质上是个数组,难道就跟数组的基本使用那样,存个数值,然后通过下表读取之类的嘛?当然不是,对于哈希表,它经常存放的是一些键值对的数据,啥是键值对?就是我们经常说的key-value,简单点说就是一个值对应另外一个值,比如a对应b,那么a就是key,b是value,哈希表存放的就是这样的键值对,在哈希表中是通过哈希函数将一个值映射到另外一个值的,所以在哈希表中,a映射到b,a就叫做键值,而b呢?就叫做a的哈希值,也就是hash值。

比如,学生的学号和姓名就是一个键值对,根据这个学号就能找到这个学生的姓名,那啥是Entry?,我们都知道键值对,在很多语言中也许都有键值对,说白了就是个大众脸,咋弄,在咱jdk中可不能那么俗气,不能再叫键值对了,叫啥嘞,那就叫Entry吧,哈哈哈。

(2)哈希表如何存数据

哈希表就是根据key值来通过哈希函数计算得到一个值,这个值就是用来确定这个Entry要存放在哈希表中的位置的,实际上这个值就是一个下标值,来确定放在数组的哪个位置上。

如,给定的学号是101011,如果经过哈希函数的计算之后得到了1,这个1就是告诉我们应该把这个Entry放到哪个位置,这个1就是数组的确切位置的下标,也就是需要放在数组中下标为1的位置。

3、如何解决Hash冲突?

不管hash函数设计的如何巧妙,总会有特殊的key导致hash冲突,特别是对动态查找表来说。

解决Hash冲突的方法有很多,这里介绍以下拉链法。java中,HashMap就是使用了这种方法,如图:

像图中所示结构,如果张三和李四都要放在1的位置上,但是张三先来的,已经占了这个位置,那李四呢?解决办法就是链表,这时候这个1的位置存放的不单单是之前的那个Entry了,此时的Entry还额外的保存了一个next指针,这个指针指向数组外的另外一个位置,将李四安排在这里,然后张三那个Entry中的next指针就指向李四的这个位置,也就是保存的这个位置的内存地址,如果还有冲突,那就把又冲突的那个Entry放在一个新位置上,然后李四的Entry中的next指向它,这样就形成了一个链表。

另外还有开放寻址法,开放寻址法采用的方式是在数组上另外找个新位置,在此就不做过多赘述了。

4、哈希表的扩容

当哈希表被占的位置比较多的时候,出现哈希冲突的概率也就变高了,所以很有必要进行扩容。

那么这个扩容是怎么扩的呢?这里一般会有一个增长因子的概念,也叫作负载因子,简单点说就是已经被占的位置与总位置的一个百分比,比如一共十个位置,现在已经占了七个位置,就触发了扩容机制,因为它的增长因子是0.7,也就是达到了总位置的百分之七十就需要扩容。

还拿HashMap来说,当它当前的容量占总容量的百分之七十五的时候就需要扩容了。

而且这个扩容也不是简单的把数组扩大,而是新创建一个数组是原来的2倍,然后把原数组的所有Entry都重新Hash一遍放到新的数组。

所谓重新Hash,就是因为数组扩大了,所以一般哈希函数也会有变化,这里的Hash也就是把之前的数据通过新的哈希函数计算出新的位置来存放。

5、哈希表如何读取数据

比如,现在要通过学号103来查找学生的姓名,怎么操作呢?

首先,我们通过学号,利用哈希函数得出位置1,然后我们去位置1拿数据,拿到这个Entry之后我们得看看这个Entry的key是不是我们的学号103,如果不是,然后根据这个Entry的next得到下一个Entry的位置,再比较key,直到成功找到李四。

        //哈希表是个简单的东西,远没有我们想象的那么复杂,至于复杂hash函数的建立和解决hash冲突的办法,基本都是数学家该考虑的事,我们大可不必花太多时间纠结这些东西

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值