面试官:哈希表都不知道,你是怎么看懂HashMap的?你刚刚讲的HashMap原理是靠背书来的?

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

在回答这个问题之前我们先来思考一个问题:如何在一个无序的线性表中查找一个数据元素?

注意,这是一个无序的线性表,也就是说要查找的这个元素在线性表中的位置是随机的。对于这样的情况,想要找到这个元素就必须对这个线性表进行遍历,然后与要查找的这个元素进行比较。这就意味着查找这个元素的时间复杂度为o(n)。对于o(n)的时间复杂度,在查找海量数据的时候也是一个非常消耗性能的操作。那么有没有一种数据结构,这个数据结构中的元素与它所在的位置存在一个对应关系,这样的话我们就可以通过这个元素直接找到它所在的位置,而此时查找这个元素的时间复杂度就变成了o(1),可以大大节省程序的查找效率。当然,这种数据结构是存在的,它就是我们今天要讲的哈希表

我们先来看一下哈希表的定义:

哈希表又叫散列表,是一种根据设定的映射函数f(key)将一组关键字映射到一个有限且连续的地址区间上,并以关键字在地址区间中的“像”作为元素在表中的存储位置的一种数据结构。这个映射过程称为哈希造表或者散列,这个映射函数f(key)即为哈希函数也叫散列函数,通过哈希函数得到的存储位置称为哈希地址散列地址

定义总是这么的拗口且难以理解。简单来说,哈希表就是通过一个映射函数f(key)将一组数据散列存储在数组中的一种数据结构。在这哈希表中,每一个元素的key和它的存储位置都存在一个f(key)的映射关系,我们可以通过f(key)快速的查找到这个元素在表中的位置。

举个例子,有一组数据:[19,24,6,33,51,15],我们用散列存储的方式将其存储在一个长度为11的数组中。采用除留取余法,将这组数据分别模上数组的长度(即f(key)=key % 11),以余数作为该元素在数组中的存储的位置。则会得到一个如下图所示的哈希表:

哈希表示例

此时,如果我们想从这个表中找到值为15的元素,只需要将15模上11即可得到15在数组中的存储位置。可见哈希表对于查找元素的效率是非常高的。

二、什么是哈希冲突

上一节中我们举了一个很简单的例子来解释什么是哈希表,例子中的这组数据只有6个元素。假如我们向这组数据中再插入一些元素,插入后的数据为:[19,24,6,33,51,15,25,72],新元素25模11后得到3,存储到3的位置没有问题。而接下来我们对72模11之后得到了6,而此时在数组中6的位置已经被其他元素给占据了。“72“只能很无奈的表示我放哪呢?

对于上述情况我们将其称之为哈希冲突。哈希冲突比较官方的定义为:

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

一般情况下,哈希冲突只能尽可能的减少,但不可能完全避免。因为哈希函数是从关键字集合到地址集合的映射,通常来说关键字集合比较大,它的元素理论上包括所有可能的关键字,而地址集合的元素仅为哈希表中的地址值。这就导致了哈希冲突的必然性。

1.如何减少哈希冲突?

尽管哈希冲突不可避免,但是我们也要尽可能的减少哈希冲突的出现。一个好的哈希函数可以有效的减少哈希冲突的出现。那什么样的哈希函数才是一个好的哈希函数呢?通常来说,一个好的哈希函数对于关键字集合中的任意一个关键字,经过这个函数映射到地址集合中任何一个集合的概率是相等的。

常用的构造哈希函数的方法有以下几种: (1)除留取余法 这个方法我们在上边已经有接触过了。取关键字被某个不大于哈希表长m的数p除后所得余数为哈希地址。即:f(key)=key % p, p≤m;

(2)直接定址法 直接定址法是指取关键字或关键字的某个线性函数值为哈希地址。即: f(key)=key 或者 f(key)=a*key+b、

(3)数字分析法 假设关键字是以为基的数(如以10为基的十进制数),并且哈希表中可能出现的关键字都是事先知道的,则可以选取关键字的若干位数组成哈希表。

当然,除了上边列举的几种方法,还有很多种选取哈希函数的方法,就不一一列举了。我们只要知道,选取合适的哈希函数可以有效减少哈希冲突即可。

2.如何处理哈希冲突?

虽然我们可以通过选取好的哈希函数来减少哈希冲突,但是哈希冲突终究是避免不了的。那么,碰到哈希冲突应该怎么处理呢?接下来我们来介绍几种处理哈希冲突的方法。

(1)开放定址法

开放定址法是指当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。

我们以本节开头的例子来讲解开放定址法是如何处理冲突的。72模11后得到6,而此时6的位置已经被其他元素占用了,那么将6加1得到7, 此时发现7的位置也被占用了,那就再加1得到下一个地址为8,而此时8仍然被占用,再接着加1得到9,此时9处为空,则将72存入其中,即得到如下哈希表:

像上边的这种探测方法称为线性探测再散列。当然除了线性探测再散列之外还有二次探测再散列,探测地址的方式为原哈希地址加上经过二次探测再散列后会得到求得72的哈希地址为5,存储如下图所示:

(2)再哈希法

再哈希法即选取若干个不同的哈希函数,在产生哈希冲突的时候计算另一个哈希函数,直到不再发生冲突为止。

(3)建立公共溢出区 专门维护一个溢出表,当发生哈希冲突时,将值填入溢出表。

(4)链地址法 链地址法是指在碰到哈希冲突的时候,将冲突的元素以链表的形式进行存储。也就是凡是哈希地址为i的元素都插入到同一个链表中,元素插入的位置可以是表头(头插法),也可以是表尾(尾插法)。我们以仍然以[19,24,6,33,51,15,25,72] 这一组数据为例,用链地址法来进行哈希冲突的处理,得到如下图所示的哈希表:

我们可以向这组数据中再添加一些元素,得到一组新的数据[19,24,6,33,51,15,25,72,37,17,4,55,83]。使用链地址法得到如下哈希表:
链地址法

三、链地址法的弊端与优化

上一节中我们讲解了几种常用的处理哈希冲突的方法。其中比较常用的是链地址法,比如HashMap就是基于链地址法的哈希表结构。虽然链地址法是一种很好的处理哈希冲突的方法,但是在一些极端情况下链地址法也会出现问题。举个例子,我们现在有这样一组数据:[48,15,26,4,70,82,59]。我们将这组数据仍然散列存储到长度为11的数组中,此时则得到了如下的结果:

链地址法存在的问题

可以发现,此时的哈希表俨然已经退化成了一个链表,当我们在这样的数据结构中去查找某个元素的话,时间复杂度又变回了o(n)。这显然不符合我们的预期。因此,当哈希表中的链表过长时就需要我们对其进行优化。我们知道,二叉查找数的查询效率是远远高于链表的。因此,当哈希表中的链表过长时我们就可以把这个链表变成一棵查找二叉树。上面的一组数据优化后可得到如下结果:

这里的查找二叉树应该是可以自平衡的二叉树,比如红黑树。它的查询的时间复杂度为o(logn)。通过这样的优化可以提高哈希表的查询效率。

写在最后

由于本文罗列的知识点是根据我自身总结出来的,并且由于本人水平有限,无法全部提及,欢迎大神们能补充~

将来我会对上面的知识点一个一个深入学习,也希望有童鞋跟我一起学习,一起进阶。

提升架构认知不是一蹴而就的,它离不开刻意学习和思考。

**这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家,**梳理了多年的架构经验,筹备近1个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

最近还在整理并复习一些Android基础知识点,有问题希望大家够指出,谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!

转发+点赞+关注,第一时间获取最新知识点

Android架构师之路很漫长,一起共勉吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

图片转存中…(img-s8auC7Mv-1713385686678)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值