散列表介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_1290259791/article/details/86039611

散列表介绍

概念

  • 原始数据叫作 键(键值)关键字(key)
  • 将原始数据转化为数组下标的映射方法称为 散列函数(或“Hash 函数”“哈希函数”,hash function)
  • 散列函数计算得到的值就叫作 散列值(或“Hash 值”“哈希值”,table)

散列表

散列表(Hash table) 也叫哈希表:根据键Key直接访问在内存存储位置的数据结构。

通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,加快查找速度。这个映射函数称作 散列函数 ,存放记录的数组称为 散列表

散列函数

它就是一个函数。

把它定义为hash(key),其中key表示元素的键值,hash(key)的值表示经过散列函数计算的到的散列值。

  1. 确定性: 如果两个散列值(同一个散列函数)不相同,那么两个散列值的原始输入也是不相同。
  2. 散列碰撞: 散列函数的输入和输出不是唯一对应关系,两个散列值相同,两个输入的值很可能是相同,也可能不同。
  3. 不可逆: 一个哈希值对应无数明文,但是你并不知道是哪个。
  4. 混淆特性: 输入一些数据计算出散列值,然后部分改变输入值,一个具有强混淆特性的散列函数会产生一个完全不同的散列值。

散列函数

MD5

MD5: 信息摘要算法5,用于确保信息传输完整一致。将数据运算为另一固定长度值,是杂凑算法的基础原理。

MD5: 输入不定长度信息,输出固定长度128-bits的算法,经过程序流程,生成4个32位数据,最后成为128-bits散列。

过程: 求余、取余、调整长度、与链接变量进行循环运算得出结果。

应用: 计算广泛用于检查错误,软件通过计算MD5来检验下载到的碎片完整性。

SHA-1

SHA-1: 安全散列算法1 是一种密码散列函数,SHA-1可以生成一个称为消息摘要的160位散列值,散列值通常呈现形式为40个十六进制数。

散列冲突

理想中的散列函数希望达到 如果 key1 ≠ key2,那 hash(key1) ≠ hash(key2)

事实,再好的散列函数也不能避免 散列冲突 ,比如10个苹果放在9个抽屉里,总有一个抽屉有两个苹果

对于散列表来说,无论存储区域(n)多大,当需要存储数据大于n时,必然会存在哈希值相同的情况。这就是所谓的 散列冲突

散列冲突解决方法

常用散列冲突方法有两类:

  1. 开放寻址法
  2. 链表法

开放寻址法

开放寻址法是一种解决碰撞的方法,对于开放寻址冲突解决方法,比较经典的有:

  1. 线性探测方法(Linear Probing)
  2. 二次探测(Quadratic probing)
  3. 双重散列(Double hashing)

1.线性探测方法

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

弊端: 当散列表插入数据越来越多,散列冲突发生的可能性就会越来越大,空闲位置越来越少,线性探测的时间就会越久(依次往后查找)。极端情况下,需要从头到尾探测整个散列表,所以最坏情况下的时间复杂度为 O(n)。

2.二次探测方法

二次探测是二次方探测法的简称。顾名思义,使用二次探测进行探测的步长变成了原来的“二次方”,也就是说,它探测的下标序列为 hash(key)+0hash(key)+1^2[hash(key)-1^2]hash(key)+2^2[hash(key)-2^2]

3.双重散列法

就是不仅使用一个散列函数,而是使用一组散列函数hash1(key),hash2(key),hash3(key)。。。。。,先用第一个散列函数,如果得到的存储位置被占用,再用第二个散列函数,依次类推,直到找到空闲存储位置。

总结

不管采用哪种探测方法,只要当散列表中空闲位置不多的时候,散列冲突的概率就会大大提高。为了尽可能保证散列表的操作效率,一般情况下,需要尽可能保证散列表中有一定比例的空闲槽位。

加载因子: 表示 Hsah 表中元素的填满的程度,若加载因子越大,则填满的元素越多,这样的好处是:空间利用率高了,但冲突的机会加大了。反之,加载因子越小,填满的元素越少,好处是冲突的机会减小了,但空间浪费多了。

链表法

更加常用的散列冲突解决办法,相比开放寻址法,它要简单很多。

在散列表中,每个位置对应一条链表,所有散列值相同的元素都放到相同位置对应的链表中。

  • 插入:通过散列函数计算出对应的散列槽位,将其插入到对应链表中即可时间复杂度:O(1)。
  • 查找、删除:需要遍历,时间复杂度跟链表的长度 k 成正比,也就是 O(k) k = (数据个数)/槽的个数

选择冲突的方法

1、开放寻址法

  1. 优点
    • 易序列化
    • 有效利用CPU缓存加速查询速度(数据存储在数组中)
  2. 缺点
    • 删除操作需要特殊标记已删除的数据
    • 冲突代价高于链表法
  3. 场景
    • 数据量比较小、装载因子小的时候

2、链表法

  1. 优点
    • 内存的利用率比开放寻址法要高(链表结点可在需要时再创建)
    • 对大的装载因子的容忍度更高(只要散列函数的值随机均匀,即使装载因子很大,虽查找效率会下降,但比顺序查找要快)
  2. 缺点
    • 对于比较小的数据的存储,比较消耗内存(存储指针)
    • 链表的结点不连续,对CPU缓存不友好,执行效率也有一定的影响
  3. 改进链表法
    • 链表 ==》其他高效的动态数据结构(eg:跳表、红黑树)。
  4. 适用
    • 适合存储大对象、大数据量的散列表,而且,比起开放寻址法,它更加灵活,支持更多的优化策略,比如用红黑树代替链表。

工业级散列表

分析对象:Java 中 HashMap

1、初始大小

  • HashMap 默认初值为 16,可修改==》较少动态扩容次数,可提升HashMap的性能。

2、装载因子和动态扩容

  • 最大装载因子默认为 0.75
  • 当 HashMap 中元素个数超过 0.75 * capacity(capacity表示散列表的容量)时,就会启动扩容,每次扩容都会是原来的两倍大小。

3、散列冲突解决方法

  • 采用链表法解决冲突。
  • 即使负载因子和散列函数设计得再合理,也免不了会出现拉链过长的情况,一旦出现拉链过长,则会严重影响 HashMap 的性能。
  • 优化(JDK1.8):
    • 当链表长度≥8,则链表转换为红黑树 进行快速增删改查 提升HashMap性能
    • 当链表长度≤8,则红黑树转换为链表(数据量小时,红黑树要维护平衡,性能优势不明显)

猜你喜欢

转载自blog.csdn.net/qq_1290259791/article/details/86039611