哈希表

哈希表的概念

  • 是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
  • 哈希表本质上来说,哈希表就是数组的一种扩展。底层依赖数组支持按下标快速访问元素的特性。
  • 将元素的键值转化为数组下标的映射方法称为哈希函数。
  • 哈希表利用数组按下标访问元素的时间复杂度是O(1)这一特性。通过哈希函数把元素的键值映射为下标,然后将对应的数据存储在数组中对应下标的位置。当查询元素时,我们使用同样的哈希函数,将元素的键值转化为数组下标,从数组中这个下标对应的位置取出数据。

哈希函数

  • 哈希函数首先时一个函数。我们可以把它定义为hash(key),key表示的是元素的键值,而hash(key)的值表示经过哈希函数计算后得到的哈希值。
  • 哈希函数设计时的3个基本要求
    1. 哈希函数计算得到的哈希值是一个非负整数。(因为数组下标是从0开始的)
    2. 如果key1 = key2,则hash(key1) = hash(key2)。(相同的key经过哈希函数得到的哈希值应该时相同的)
    3. 如果key1!=key2,则hash(key1) != hash(key2)。(要求key不同,经过哈希函数得到的哈希值也应该时不同的。但这几乎时不可能的,这种情况的发生被称为哈希冲突。)

哈希冲突

再好的哈希函数也无法避免哈希冲突。解决哈希冲突一般有两种方式。

  • 开放寻址法

    1.线性探测法

    当向哈希表中插入数据时,如果某个数据经过哈希函数计算之后得到的哈希值,对应的存储位置已经被占用了,我们就从这个位置开始,在数组中依次向后查找,直到找到空闲位置。

    当在哈希表中查询数据时,则通过哈希函数计算得到哈希值。取出对应哈希值的数组下标的元素进行比较,如果相同则说明这就是要查询的数据,如果不同,则需要从这个下标处开始顺序往后依次查找,直到找到相等的数据,或者遍历到数组的空闲位置还没有找到,说明要查询的数据不在哈希表中。

    当在哈希表中进行数据的删除时,如果使用的是线性探测的方式解决哈希冲突,则删除操作不能简单的直接删除元素,需要对删除元素的存储空间标记为deleted的状态。这是为了避免由于删除操作,导致存储空间为null,线性探测查询数据的操作中途停止的情况发生。

    2.二次探测法

    线性探测法步长为1。向后+1依次探测。二次探测法其实就是将步长变为原来的二次方。探测下标+(探测次数的二次方)进行探测。

    3.双重哈希法

    就是使用多个哈希函数。当第一个哈希函数计算得到的存储位置已经被占用的时候,再用第二个哈希函数重新计算存储位置,直到找到空闲的存储位置。

  • 链表法

    链表法是一种更常用的解决哈希冲突的方法。其实就是数组下表中存放的数据是对应的是一个链表。

    当插入数据时,我们通过哈希函数计算出哈希值,找到对应的数组下标位置,将元素插入到对应的链表中。

    查询数据和删除数据,则是通过哈希函数计算出哈希值,找到对应的数组下标位置,取出存放数据的链表,对链表进行遍历,找到对应的数据,去完成查询的比较或者删除的操作。

如何去设计一个合理的哈希表

  • 几个方面

    1.找到一个合适的哈希函数。

    2.设置合理的装载因子的阈值,并设计动态扩容策略。

    3.选择合适的哈希冲突解决方法。

  • 设计哈希函数

    设计哈希函数的标准

    1.哈希函数的设计不能太复杂,过于复杂的哈希函数,会消耗太多的计算时间。

    2.哈希函数生成的值要尽可能的随机且平均分布,这样才能尽量的去避免哈希冲突。

    3.哈希函数的设计方法还有很多。比如直接寻址法,平方取中法,折叠法,随机数法等。

  • 解决装载因子过大的问题

    装载因子=哈希表中的元素个数/哈希表的长度

    可以理解为数组中每个下标都对应的是一个存放数据的链表,而装载因子就是这个链表中所存储的数据个数。

    装载因子应该要设置一个阈值,当达到这个阈值时,需要对哈希表进行扩容操作。

  • 避免低效扩容

    当装载因子达到阈值时,我们就进行动态扩容,创建一个存储空间是原来两倍大的一个哈希表。然后将旧的哈希表中的数据搬移到新的哈希表中。

    但是哈希表搬移数据的操作并不像数组一样简单的搬移。他需要对旧数据重新计算哈希值,再将数据放到新的哈希表中。这样的操作就很耗时。

    为了避免扩充时搬移数据耗时过多的情况发生。我们可以不全部搬移数据到新哈希表中。当有新数据要插入的时候,我们将新数据插入到新的哈希表中,同时在旧的哈希表中取出一条旧数据搬移到新哈希表中。这样我们就将搬移工作分解成了多次。均摊了耗时。但由在搬移完全部数据之前,旧的哈希表是一直存在的,同样也会占用一部分内存,同时在进行查询和删除操作的时候,需要在新旧两个哈希表中都进行。

  • 选择合适的冲突解决方法

    1.开放寻址法

    当数据量比较小,装载因子小的时候,适合开放寻址法。

    优点:数据存储再数组中,可以有效的利用CPU的缓存,加速查询速度。不涉及链表和指针,方便序列化。

    缺点:装载因子必须小于1,会占用更多的存储空间。

    2.链表法

    其实也可以将链表改造成红黑树,可以设置一个装载因子的阈值,在链表结构和红黑树结构之间进行切换。

    优点:链表的节点可以在用到时再创建,对内存的利用率比较高。对大装载因子的容忍度更高。

    缺点:链表存储数据时,还需要存储next指针,因此会消耗额外的内存空间。链表的节点在内存中零散分布的,不是连续的,对CPU的缓存不友好。

LRU缓存淘汰算法的应用

  • 核心思路

    使用哈希表和有序双向链表搭配。有序双向链表用于存储缓存数据,哈希表作为索引,存储着指向有序链表节点的指针。通过哈希表可以快速查找到需要操作的数据的节点,对其进行插入,移动或者删除操作。

  • 实现原理

    一个缓存系统,主要包括向缓存中添加一个数据,从缓存中删除一个数据,在缓存中查找一个数据。这3个操作都涉及到了在链表中查找数据的操作。在链表中进行遍历查找时间复杂度为O(n)。

    我们构建一个哈希表作为索引。哈希表中存储着一个指向有序链表节点的指针。我们可以通过哈希表,快速查找到需要操作的数据的有序链表的节点。

    当我们在缓存中查找一个数据时,可以通过哈希表,直接找到这个数据的节点,直接在有序链表中找到这个数据,将它移动到链表的头部。

    当我们往缓存中添加数据时,我们需要先进行查找操作,借助哈希表去查找,如果没找到,则直接将数据插入到链表头部,如果找到了,则将该数据移动到链表头部。

    当我们从缓存中删除数据时,我们同样借助哈希表,直接找到要删除的数据再有序链表中的节点。双向有序链表可直接删除节点,时间复杂度为O(1)

  • 总结

    哈希表支持高效的数据插入,删除和查找的操作。但哈希表中的数据是经过哈希函数打乱后无规则存储的,所以不支持顺序遍历并输出数据。

    有序双向链表支持按照某种顺序遍历并输出数据。但查找的时间复杂度为O(n)。

    哈希表和有序双向链表结合起来,就既可以实现快速的插入,删除和查找的操作。又能支持O(n)时间复杂度的按顺序遍历并输出数据。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容