day69 【哈希,HashSet,HashMap】

哈希简介

什么是哈希表?

哈希表(hash table)也叫散列表,本质上是个数组,由数组+其它数据结构,形成的一种新的数据结构;那总得有名字吧,就叫做哈希表吧。
跟数组一样,可以通过下标直接获取数据,不过这里不是根据下标了,而是根据key (关键字)。

实现:key通过哈希函数(也叫散列函数)F(key),计算映射成一个值,这个值就是下标,是要存放的数组位置。
这个映射过程称为哈希造表或者散列,这个映射函数F(key)即为哈希函数也叫散列函数,通过哈希函数得到的存储位置称为哈希地址或散列地址,而这个地址就可以存储Entry(key-value)。

Entry:在哈希表中是通过哈希函数将一个值映射到另外一个值的,所以在哈希表中,key映射到value,key就叫做键值,而value呢?就叫做key的哈希值,也就是hash值。

注意阿:本质目的就是根据key获取value,而F( )的目的仅仅是看要存到哪个位置而已!

什么是散列函数?

通过一些特定的方法去得到一个特定的值,就类似一个函数似的,那么这个函数或者是这个方法在哈希表中就叫做散列函数,其中规定的一些操作就叫做函数法则。

例如:查字典,一个字,你可以根据首字母查,可以根据部首查等,而这个字的首字母、部首,就是这个字通过函数法则F( )映射过程形成的首字母、部首。
这个字,就为key。首字母、部首,就为hash值。这个字变成首字母、部首的过程,就为F( )散列函数。

总结:哈希表就是根据key通过一个散列函数加工处理之后得到一个值,这个值就是数据存放的位置,我们就可以根据这个值快速的找到我们想要的数据。

提升

散列算法所计算出来的散列值(Hash Value)具有不可逆(无法逆向演算回原本的数值)的性质。

数组和链表几乎是两个极端,一个查找效率高,一个插入删除效率高,哈希表就是融合两者优点的一种数据结构。在哈希表中进行添加,删除,查找等操作,性能都非常高,不考虑哈希冲突的情况下,仅需一次定位即可完成,时间复杂度为O(1)。

总结:哈希表 = 数组(主干) + 链表(解决冲突)  或者  哈希表 = 数组(主干) +二叉树 或者  哈希表 = 数组(主干) + 链表 +二叉树

什么是哈希冲突?

定义:对于不同的关键字(key),可能得到同一个哈希地址(F(key)),即key1≠key2,而 F(key1)=F(key2),对于这种现象我们称之为哈希冲突,也叫哈希碰撞。

哈希冲突是指哈希函数算出来的地址被别的元素占用了,也就是这个位置有人了。 例子如图👇:72算出来要放在数组下标6的位置,但此时这个位置已有元素了。

为什么会有冲突 一句话总结:元素有无限可能,但存储位置有限。 

https://img-blog.csdnimg.cn/20200922233756827.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTIxNTcz,size_16,color_FFFFFF,t_70#pic_center

哈希冲突解决

这里写图片描述

这个人写得好好噢!地址:面试官:哈希表都不知道,你是怎么看懂HashMap的?_smily的博客-CSDN博客_哈希表hashmap  

哈希冲突的解决方案有很多种,而HashMap采用了链地址法,就是数组+链表的方式,(链表就有节点了,next指针)所有通过哈希函数得到同一地址的元素通过链表加在后面即可。

但是链地址法也有弊端,当存储在一个位置的元素过多时,哈希表会退化成一个链表,当我们在这样的数据结构中去查找某个元素的话,时间复杂度又变回了o(n)。

https://img-blog.csdnimg.cn/20200923232503838.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTIxNTcz,size_16,color_FFFFFF,t_70#pic_center 

因此,当哈希表中的链表过长时就需要我们对其进行优化。我们知道,二叉查找树的查询效率是远远高于链表的。因此,当哈希表中的链表过长时我们就可以把这个链表变成一棵红黑树。

红黑树是一个可以自平衡的二叉查找树。它的查询时间复杂度为o(lgn)。通过这样的优化可以提高哈希表的查询效率。上面的一组数据优化后可得到如下结果:

https://img-blog.csdnimg.cn/20200927225706751.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzIwNTIxNTcz,size_16,color_FFFFFF,t_70#pic_center 

链表长度大于等于8的话,链表就会转换成树结构,当然如果长度小于等于6的话,就会还原链表。以此来解决链表过长导致的性能问题。 

为啥是小于等于6啊,咋不是7嘞?

这样设计是因为中间有个7作为一个差值,来避免频繁的进行树和链表的转换,因为转换频繁也是影响性能的啊。

 哈希表如何读取数据?

首先通过key利用哈希函数得出位置,然后去位置拿数据,拿到这个Entry之后看看这个Entry的key是不是我们的key,如果不是,根据这个Entry的next知道下一个位置,再比较key,如果是,拿出value,这就成功读取了。

⭐情景对话式讲解,讲得很清楚:来吧!一文彻底搞定哈希表! - 知乎

哈希表的扩容与Rehash 

装填因子:

在哈希表长度不变的情况下,随着哈希表中插入的元素越来越多,发生哈希冲突的概率会越来越大,相应的查找的效率就会越来越低。这意味着影响哈希表性能的因素除了哈希函数与处理冲突的方法之外,还与哈希表的装填因子大小有关。

我们将哈希表中元素数与哈希表长度的比值称为装填因子。

很显然,α的值越小哈希冲突的概率越小,查找时的效率也就越高。而减小α的值就意味着降低了哈希表的使用率。显然这是一个矛盾的关系,不可能有完美解。为了兼顾彼此,装填因子的最大值一般选在0.65~0.9之间。比如HashMap中就将装填因子定为0.75。一旦HashMap的装填因子大于0.75的时候,为了减少哈希冲突,就需要对哈希表进行扩容操作。比如我们可以将哈希表的长度扩大到原来的2倍。

Rehash:

这里我们应该知道,扩容并不是在原数组基础上扩大容量,而是需要申请一个长度为原来2倍的新数组。因此,扩容之后就需要将原来的数据从旧数组中重新散列存放到扩容后的新数组。这个过程我们称之为Rehash。

例子:数组长度:11,已有元素:8个,装填因子:0.75,此时α值为:8/11=0.7272...。

当插入第9个元素时,此时α值为:9/11=0.8181... 。 α > 0.75,该扩容了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值