• HashTable 散列表


    HashTable 散列表

    2018-12-09 17:52:24 aaa_dai 阅读数 77 文章标签: 散列函数散列表hashtable 更多

    分类专栏: Data-structure/Algorithm

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

    本文链接:https://blog.csdn.net/sgs595595/article/details/84930101

    Hash Table 散列表

    散列表用的是数组支持按照下标随机访问数据的特性,所以散列表其实就是数组的一种扩展,由数组演化而来.

    性能

    散列函数和填装因子是衡量散列表的主要标准

    1. 填装因子: 散列表包含的元素数/散列表位置总数. 最大默认填装因子是0.75, 一旦填装因子大于0.7, 就要调整散列表长度
      装载因子过大时,可以进行动态扩容, 一般将空间长度加倍.
      针对hash表, 动态扩容后, 需要重新计算hash key: hash(key) % n, n是散列表长度.
      避免一次性扩容, 将扩容操作穿插在插入操作的过程中,分批完成.

    散列冲突

    1. 简单来说就是多个输入值映射到同一个结果.
      理论上输入值的数量是无限大的, 得到的结果数目也是无限大的, 但是存储的介质是有限的,
      根据鸽巢理论, 10只鸽子9个巢, 总有一个没地儿住.
    2. 解决
      1. 开放寻址法: 如果出现散列冲突, 就重新探测一个空闲位置进行插入.
        适用情况: 数据量比较小,装载因子小的时候,适合采用开放寻址法.Java 中的ThreadLocalMap使用开放寻址法的原因
        线性探测:往散列表中插入数据时,如果某个数据经过散列函数散列之后,存储位置已经被占用了,我们就从当前位置开始, 依次往后查找,看是否有空闲位置,直到找到为止.
        查找: 线性探测的查找同插入是差不多的, 根据键得到散列值后与原值进行比较, 不相等就继续遍历散列表并比较值, 到结尾没有发现相等值就从表头开始继续找, 直到发现空闲位置时, 若还没有找到, 就返回不存在.
        删除: 如果简单的将元素删除, 就会导致原来的查找算法失效. 作为解决的方法, 我们可以将删除的元素标记为deleted, 查找时遇到后不是停下来, 而是继续探测.
        二次探测: 基本同线性探测, 只是查找空闲位置的步长从固定的1次变成探测次数的平方. hash(key)+0, hash(key)+1,hash(key)+4…
        双重散列: 寻找同空闲位置时, 每一步用的是不同的散列函数.hash(key)+0, hash2(key),hash3(key)…
        问题: 数据变多时, 散列冲突发生可能性也变大, 空闲位置越来越少, 查找和删除的时间也会变长
      2. 链表法: 散列表槽位存储的元素是链表
        当查找,删除一个元素时,通过散列函数计算出对应的槽位,将其插入到对应链表中即可,所以插入的时间复杂度是O(1)
        查找的时间复杂度要加上链表的查找时间, 链表的时间复杂度与长度k成正比, 即O(k)
        优点:基于链表的散列冲突处理方法比较适合存储大对象,大数据量的散列表,
        而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表

    产品级散列表

    1. 要求
      支持快速的查询,插入,删除操作;
      内存占用合理,不能浪费过多的内存空间;
      性能稳定,极端情况下,散列表的性能也不会退化到无法接受的情况.
    2. 特性
      设计一个合适的散列函数;
      定义装载因子阈值,并且设计动态扩容策略;
      选择合适的散列冲突解决方法.

    散列表与链表结合使用

    LRU 缓存淘汰算法

    1. 原理: LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高
    2. 链表实现 LRU 缓存淘汰算法
      维护有序排列的链表结构.因为缓存大小有限,当缓存空间不够时,我们就将链表头结点删除.尾结点存放最近的数据.
      缓存数据:先在链表中查找数据.如果没有,直接将数据放到链表的尾部;如果找到,我们就把它移动到链表的尾部.
      因为查找数据需要遍历链表,链表实现的 LRU 缓存淘汰算法的时间复杂很高,是 O(n).
    3. 散列表和链表两种数据结构组合使用,可以降到O(1).
      实现:散列表使用链表法解决散列冲突,将节点放到两条链中: 双向链表用来实现算法, hash列表用来查找
      结点结构: 存储数据(data),前驱指针(prev),后继指针(next),新增字段 hnext(用来将结点串在散列表中)
      操作: 查找, 散列表中查找数据的时间复杂度接近 O(1),所以通过散列表,我们可以很快地在缓存中找到一个数据.当找到数据之后,我们还需要将它移动到双向链表的尾部
      删除: 我们需要找到数据所在的结点(散列表 O(1)),然后将结点删除.双向链删除结点只需要 O(1) 的时间复杂度(前驱指针)
      添加: 查看数据是否已存在.若存,移动到双向链表尾部;若否,看缓存有没有满.若满,则将双向链表头结点删除,再将数据放到尾结点;若未满,直接置于尾部.

    Redis 有序集合

    1. 有序集合中,每个成员对象有两个重要的属性: key(键值)和score(分值)
    2. 实现: 类似LRU 算法.按照键值构建一个散列表,这样按照key来删除,查找一个成员对象的时间复杂度就变成了 O(1).同时,借助跳表结构,其他操作也非常高效

    结合两种数据结构的原因

    散列表这种数据结构虽然支持非常高效的数据插入,删除,查找操作,但是散列表中的数据都是通过散列函数打乱之后无规律存储的, 而
    且散列表是动态数据结构, 数据一直在改变. 为了以某种顺序快速地遍历数据, 我们结合散列表与链表(或跳表)
    PIS:
    数组占据随机访问的优势,却有需要连续内存的缺点.
    链表具有可不连续存储的优势,但访问查找是线性的.
    数据结构和算法的重要性:连续空间 > 时间 > 碎片空间

    散列函数

    实现

    1. 设计的不能太复杂. 简单的散列函数可以耗费更少的计算时间
    2. 散列函数生成的值要尽可能随机且分布均匀. 最小化散列冲突
    3. word拼写的散列函数: 进位相加法, 将单词看作26进制的数字
      hash(“nice”)=((“n” - “a”) * 262626 + (“i” - “a”)2626 + (“c” - “a”)*26+ (“e”-“a”)) / 78978
    4. 其他的:直接寻址法,平方取中法,折叠法,随机数法

    特征

    1. 相同的输入映射到相同的结果: 输入’avocado’, 无论多少次,都得到同一个结果
    2. 不同的输入映射到不同的结果: ‘avocado’和’Avocado’, 得到的是不同的结果
    3. 只返回有效的结果,散列值应该是一个非负整数:假设映射的结果索引中只有5个元素,结果就不会返回100.
      散列函数结果是散列表的索引, 其实就是数组的索引, 所以应该>=0

    Python应用

    1. 用于查找
      python的字典, dict()或{}
      1. 无序的键:值对(key:value 对)集合
      2. 序列是以连续的整数为索引,与此不同的是,字典以 关键字 为索引,关键字可以是任意不可变类型,通常用字符串或数值.
      3. 如果元组中只包含字符串和数字,它可以做为关键字,如果它直接或间接的包含了可变对象,就不能当做关键字
      4. 字典的主要操作是依据键来存储和析取值.也可以用 del 来删除键:值对(key:value).
      5. 一个已经存在的关键字存储值,以前为该关键字分配的值就会被遗忘
        phone_books = dict()
        phone_books = {}
    2. 防止重复
      将已投票的名字放到散列表中, 防止其多次投票. 当投票的人数很多时, 名单会变得非常长, 散列表的一对一查找就很快了
    >>> voted['123']=True
    >>> voted['132']=True
    >>> voted.get('123')
    True
    >>> voted['123']='T'
    >>> voted.get('123')
    'T'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. 用作缓存
      将缓存的数据放到散列表中
  • 相关阅读:
    带下拉子菜单的导航菜单
    如何使用myFocus插件制作焦点图效果
    将博客搬至CSDN
    《转》二进制与三进制的那些趣题
    二叉树遍历 (前序 层次 == 深度 广度) 层次遍历
    数组全排列 knuth 分解质因数
    堆排序
    双向快速排序
    二路归并排序
    字符串的排列
  • 原文地址:https://www.cnblogs.com/grj001/p/12223493.html
Copyright © 2020-2023  润新知