数据结构——散列表

一、散列表的由来?

    1、散列表来源于数组,它借助散列函数对数组这种数据结构进行扩展,利用的是数组支持下标随机访问元素的特性。

    2、需要标识存储在散列表中数据我们称为键(key)或称为关键字,将键转化为数组下标的方法称为散列函数,散列函数的计算结果称为散列值

    3、将数据存储在散列值对应的数组下标位置。

二、如何设计散列函数?

    设计散列函数的3点基本要求:

    1、散列函数计算得到的散列值是一个非负整数。

    2、若key1 = key2,则has(key1) = hash(key2)

    3、若key1 ≠ key2,则has(key1) ≠ hash(key2)

    正是由于第3点要求,所以产生了几乎无法避免的散列冲突问题。(当key1≠ key2,它们通过散列函数得到的数组下标是有可能是一样的)

三、散列冲突的解放方法?

    1、常用的散列冲突解决方法有2类:开放寻址法(open adressing)和链表法(chainning)"

    2、开放寻址法

        1)核心思想:如果出现散列冲突,就重新探测一个空闲位置,将其插入。

        2)线性探测法:

            插入数据:当我们往散列表中插入数据时,如果某个数据经过散列函数之后,存储的位置已经被占用了,我们就从当前位置开始,依次往后查找,看是否有空闲位置,直到找到为止。

            查找数据:我们通过散列函数未出要查找元素的键值对应的散列值,然后比较数组中下标为散列值的元素和要查找的元素是否相等,若相等,则说明就是我们要查找的元素;否则,就顺序往后依次查找,如果遍历到数组的空闲位置还未找到,就说明要查找的元素并没有在散列表中。

            删除数据:为了不让查找算法失效,可以将删除的元素特殊标记为deleted,当线性探测查找的时候,遇到标记为deleted的空间,并不是停下来,而是继续往下探测。

            结论:最坏时间复杂度为O(n)。

        3)二次探测(Quadratic probing):线性探测每次探测的步长为1,即在数组中一个一个探测,而二次探测的步长变为原来的平方。

        4)双重散列:使用一组散列函数,直到找到空闲位置为止。

        5)线性探测法的性能描述:

            用“装载因子”来表示空位多少,公式:散列表装载因子=填入表中的个数/散列表的长度。

            装载因子越大,说明空闲位置越少,冲突越多,散列表的改组会下降。

    3、链表法(更常用)

        插入数据:当插入的时候,我们需要通过散列函数计算出对应的散列槽位,将其插入到对应的链表中即可,所以插入的时间复杂度为O(1)。

        查找或删除数据:当查找、删除一个元素时,通过散列函数计算对应的槽,然后遍历链表查找或删除。对于散列比较均匀的散列函数,链表的节点个数k=n/m,其中n表示散列表中数据的个数,m表示散列表中槽的个数,所以时间复杂度为O(k).

四、要了解散列表,要先了解数组和链表优劣

    1、数组占据随机访问的优势,却又需要连续内存的缺点。

    2、链表具有可不连续存储的优势,但访问查找是线性的。

    3、散列表和链表、跳表的混合使用,是为了结合数组和链表的优势,规避它们的不足。

五、为什么散列表和链表经常放在一起使用?

    1、散列表的优点:支持高效的数据插入、删除和查找操作。

    2、散列表的缺点:不支持快速顺序遍历散列表中的数据。

    3、要快速顺序遍历散列表的数据只能将数据转移到数组,然后排序,最后再遍历数据。

    4、散列表是动态的数据结构,需要频繁的插入和删除数据,那么每次顺序遍历之前都需要先排序,这势必会造成效率非常底低下。

    5、如何解决上面的问题呢? 就是将散列表和链表(或跳表)结合起来使用。

六、散列表和链表如何组合起来使用?

    1、LRU(Least Recently Used)缓存淘汰算法

    1.1、LRU缓存淘汰算法主要操作包含3个操作:   

        1)往缓存中  添加一个数据

        2)从缓存中删除一个数据

        3)在缓存中查找一个数据

        4)总结:上面3个都涉及到查找

    1.2、如何用链表实现LRU缓存淘汰算法?

        1)需要维护一个按照访问时间从大到小的有序排列的链表结构。

        2)缓冲空间有限,当空间不足需要淘汰一个数据时直接删除头部的节点。

        3)当要缓存某个数据时,先在链表中查找这个数据。若未找到,则直接将数据放到链表的尾部。若找到,就把它移动到时链表尾部。

        4)前面说了,LRU缓存的3个主要操作都涉及到查找,若单纯由链表实现,查找的时间复杂度很高为O(n)。若将链表和散列表结合使用,查找的时间复杂度会降低到O(1)。

    1.3、如何使用散列表实现LRU缓存淘汰算法 ?

        1)使用双向链表存储数据,链表中每个节点存储数据(data)、前驱指针(prev)、后继指针(next)和hnext指针(解决散列冲突的链表指针)。

        2)散列表通过链表解决散列冲突,所以每个节点都会在两条链中。一条链是双向链表,另一条链是散列表中的拉链。前驱和后继指针是为将节点串在双向链表中,hnext指针是为了将节点串在散列表的拉链中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 1 散列表 散列表的英文叫“Hash Table”,我们平时也叫它“哈希表”或者“Hash 表”,散列表用的就是数...
    贪睡的企鹅阅读 5,377评论 0 1
  • Word 这种文本编辑器你平时应该经常用吧,那你有没有留意过它的拼写检查功能呢?一旦我们在 Word 里输入一个错...
    acc8226阅读 1,625评论 0 0
  • 基本概念 基于线性表、树表结构的查找方法,这类查找方法都是以关键字的比较为基础的。在查找过程中只考虑各元素关键字之...
    官先生Y阅读 3,499评论 0 2
  • 散列表(Hash Table)也称哈希表,散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的...
    TioSun阅读 1,261评论 0 0
  • 1.前言 以前看hashmap的原理看了很多次,过了很久都忘记了。真心发现以前的看书方法不对。感觉学习还是要靠背书...
    过期的薯条阅读 4,280评论 0 0

友情链接更多精彩内容