文章目录

  • 引导案例
    • 案例一
    • 案例二
  • hash表(散列表)
  • 哈希函数(散列函数)
  • 哈希碰撞( 哈希冲突 )
  • 如何解决hash冲突(hash碰撞)
    • 开放寻址
      • 线性探测(LP)
      • 二次探测 (平方探测 QP)
      • 再哈希法(DH)
    • 链地址法
  • 算法可视化网站

引导案例

案例一

问题: 有n个(1<n<10)自然数 ,每个自然数的范围在1~100之间,用最快的速度来判断某个数是否在这n个数中,不能使用已经封装好的类。

假设有5个自然数: 4 ,50, 87,99,100

判断100, 在不在这5个数中


分析:

自然 —> 非负整数 ( 0 , 1 , 2 , 3 , 4 , … )

可以想到的几种方式 : 排序(没必要)遍历、 数组(利用数组下标)…

遍历: 循环,判断每个数是否和目标数值相等,相等则退出循环,存在。

数组下标: 初始化一个能够容纳最大数据的int数组,数组中的值默认为0 ,然后把出现的这n个数的下标置为1,判断某个数是否存在—>直接判断这个数在数组中对应的下标是0还是1即可,1则存在,0 则不存在,那么查询的时间复杂度 O(1),也不需要遍历。

很显然遍历的效率不如利用数组下标


解答:

拿上面的例子为例,

假设有5个自然数: 4 ,50, 87,99,100判断100, 在不在这5个数中

初始化一个长度为100的int数组 (每个自然数的范围是1~100,100个数)

int[] arr = new int[100];
a[4]-->1
a[50]-->1
a[87]-->1
a[99]-->1
a[100]-->1
其余的数组中的值为默认值 0 

判断a[100]是否存在,直接看下 a[100] 为 0 还是 1 即可。 (不用较真,数组下标从0开始,查看100,应该查看a[99], 重要的是思路


案例二

思考: 案例一中的这种方式有什么弊端吗?

先来看下另外一个例子

问题: 有n个(1<n<10)自然数 ,每个自然数的范围在0~10000000000(100亿)之间,用最快的速度来判断某个数是否在这n个数中,不能使用已经封装好的类。

假设这5个数为 999999,999999999,9999999999,9989898989

这个时候 ,你初始化一个 100亿的一个数组吗? 就为了存上面的5个数,显然是不合理的。

int 最多存21亿多,这100亿肯定存不下,当然了 你换成可以long型 , 但是这个空间浪费的是不是太多了… 肯定接收不了。

数据太大存不下,并且空间浪费太多 ,那该如何解决呢? --------------> hash 就要登场了。


hash表(散列表)

散列表 , 英文 hash table .

hash table 就是利用数组支持按照下标随机访问数据的特性,对数组的一种扩展,从数组演化而来。 所以hash table 本质上就是一个数组。

<font color=red我们刚才的例子,已经用到了散列的思想 。 N个自然数,并且与数组的下标形成一一的映射,所以利用数组支持下标随机访问特性,**查询时间复杂度是O(1)**这一个特性,就可以实现快速哦按段元素是否存在序列当中。


哈希函数(散列函数)

上面的例子我们也看到了,数据量巨大的时候,数组是放不下的,那就需要一种压缩方法,把这种数据压缩到一个可接收的范围内。

比如把 0~199 (largeNum)的压缩为 0到9(smallNum) , 0到9 有10个数,所以smallRange = 10 ,

用个公式来表示的话就是

smallNum = largeNum % smallRange

上面这种取余的操作,就可以理解为是hash化,是hash函数的一种。


细看一下

假设N=10 (压到0到9的值), 有下面几个数

11 , 52 ,33 ,64 ,75 ,26 ,199…

对应上面的公式的话, smallRange = 10 , 上面的这几个数字就是largeNum

我们来通过取余来计算下smallRange


11 % 10 = 1, 52 % 10 = 2,33 % 10 = 3 ,64 % 10 = 4,75 % 10 = 5,26% 10 = 6 ,199 % 10 = 9

我们是不是可以把 0 到 9 理解为数组下标 ? 对的。


11 % 10 = 1,   =========> a[1]======>   代表 11 52 % 10 = 2, =========> a[2]======> 代表 5233 % 10 = 3 ,  =========> a[3]======> 代表 3364 % 10 = 4,   =========> a[4]======> 代表 6475 % 10 = 5,   =========> a[5]======> 代表 7526% 10 = 6 ,   =========> a[6]======> 代表 26199 % 10 = 9  =========> a[9]======> 代表 199

判断 199 是不是在 这几个数中,是不是就可以这样操作?

199 % 10 = 9 =======> a[9] ===⇒ 比对下a[9] = 199 ? =====> 等于则存在,不等于则不存在。


哈希碰撞( 哈希冲突 )

到了这里,你可能已经发现问题了,这组数据当然是故意制作的,

11 , 52 ,33 ,64 ,75 ,26 ,199......

数组下标没有冲突的…

如果是下面这组数字呢?

11 , 52 ,22 ,42,75 ,26 ,199......

hash化处理一下如下:


11 % 10 = 1, 52 % 10 = 2,22 % 10 = 2 ,42 % 10 = 2,75 % 10 = 5,26% 10 = 6 ,199 % 10 = 9

可以知道 52 , 22 , 42 取余后 都是 2 ,那问题来了 a[2] 有多个值了,到底代表哪一个呢?

这种情况就称之为 哈希碰撞 或者 哈希冲突


如何解决hash冲突(hash碰撞)

开放寻址

核心思想: 在开放寻址法中,如果数据不能直接放在由hash函数计算出来的数组下标所指的单元时,就要寻找数组的其他位置。

根据在找下一个空白单元时使用的方法不同,又可以分为

  • 线性探
  • 二次探
  • 二次哈希

线性探测(LP)

LP : LINEAR PROBING

我们以线性探测为例来看下 是如何实现开放寻址的

线性探测:在线性探测中,线性的查找空白单元,比如 数组下标 666 为要插入数据的位置,如果它已经被占用了,则继续探测667,依次类推,直到找到一个空位,这个就叫线性探测,因为它沿着数组的下标一步步的寻找空白单元

线性探测 示意图:


二次探测 (平方探测 QP)

QP:QUADRATIC PROBING

线性探测会发生聚集,如果hash化后的数据落到了聚集范围内的数据项,就要一步步的移动。

已填入hash表中的数据和表长的比率叫做装填因子,比如1万个单元的哈希表填入了3334个数据,那么它的装填因子就是1/3.

当装填因子不是很大的时候,聚集分布的比较连贯。 hash表的某部分可能包含大量的聚集,而另一部分还很稀疏。 聚集降低了hash表的性能。

二次探测主要是为了防止聚集的产生。核心思想:探测相隔较远的单元,而不是和原始位置相邻的单元。

步骤是步数的平方

举个例子:
在线性探测中,如果哈希函数计算出来的原始下标是x, 线性探测就是 x+1 , x+2 ,x+3 ,x+4,x+5…依次类推。

而在二次探测中,探测的过程则是 x+1 , x+4 ,x+9 x+16,x+25… 到原始位置的距离是步数的平方: x+1^2 x+2^2 x+3^2 x+4^2 x+5^2

当二次探测的搜索边长的时候,它好像变得很绝望。

  • 第一次操作相邻单元
  • 如果这个单元被占用,它认为这里可能有一个小的聚集,所以它尝试距离为4的单元
  • 如果这里也被占用,它变得有些焦虑,它认为这里有个大的聚集,然后就尝试距离为9的单元
  • 如果这里还是被占用了,它感到了一丝恐慌,跳到距离为16的单元,很快,它就会歇斯底里的飞跃整个数组空间 。
  • 当hash表快满的时候,就会出现这种情况

二次探测消除了线性探测中产生的聚集问题,这种聚集被称为原始聚集,但是也产生了更细的聚集 ,被称为二次聚集。 二次聚集不是一个很严重的问题,因为不常用 。 有更好的解决方案------> rehash


再哈希法(DH)

DH: DOUBLE HASHING

为了消除原始聚集和二次聚集,可以使用 二次哈希 。

其实而此举既产生的原因是二次探测的算法产生的探测序列步长总是固定的: 1,4,9,16…依次类推。

核心思想: 需要产生一种依赖关键字的探测序列,而不是每个关键字都一样,那么,不同的关键字即使映射到相同的数组下标,也可以使用不同的探测序列。把关键字用不同的哈希函数再做一遍哈希化,用这个结果作为步长。对于指定的关键字,步长在整个探测中是不变的,不过不同的关键字使用不同的步长。

第二个哈希函数必须具备如下特点:

  • 和第一个哈希函数不同
  • 不能输出0(否则,将没有步长,每次探测都是原地踏步,算法将陷入死循环)。

使用如下的哈希函数工作的非常好:

stepSize = constant - key % constant;

其中constant是质数,且小于数组容量。

再哈希法要求表的容量是一个质数.

举个例子: 假如表长度为15(0-14),非质数,有一个特定关键字映射到0,步长为5,则探测序列是0,5,10,0,5,10,以此类推一直循环下去。算法只尝试这三个单元,所以不可能找到某些空白单元,最终算法导致崩溃。

如果数组容量为13, 质数,探测序列最终会访问所有单元。即0,5,10,2,7,12,4,9,1,6,11,3,一直下去,只要表中有一个空位,就可以探测到它。


Q: 如果中间有个数据被删除了怎么办呢?

标记为 -1,否则的话就会出现数据缺失。 因为查找的时候,找到一个空位,就不找了,认为已经结束了,所以需要给删除的数据单元打标 。


链地址法

核心思想: 某个数据项的关键字值还是像通常一样映射到哈希表的单元,而数据项本身插入到这个单元的链表中。 其他同样映射到这个位置的数据项只需要加到链表中,不需要在原始的数组中寻找空位。

上述的这个模型就是JDK1.7 HashMap的原型。

再来看个极端的情况


Q: 如果中间有个数据被删除了怎么办呢?

可以删除,因为链表仅仅是个指针指向它而已。


算法可视化网站

https://visualgo.net/zh

https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

Algorithms_算法专项_Hash算法的原理哈希冲突的解决办法相关推荐

  1. 什么是哈希表?为什么要使用哈希表?哈希表的实现原理?哈希冲突怎么解决?

    前言 当我们在编程过程中,往往需要对线性表进行查找操作.在顺序表中查找时,需要从表头开始,依次遍历比较a[i]与key的值是否相等,直到相等才返回索引i:在有序表中查找时,我们经常使用的是二分查找,通 ...

  2. 啸叫抑制原理简介和软件SDK解决办法

    啸叫抑制原理简介和软件SDK解决办法 说起"啸叫",大家或许会对这个名词很陌生,但在我们生活中,"啸叫"几乎是所有人都遇见过的一个难题.例如:在KTV大家唱的正 ...

  3. 域名劫持定义及原理、域名被劫持解决办法有那些

    域名劫持是互联网攻击的一种方式,通过攻击域名解析服务器(DNS),或伪造域名解析服务器(DNS)的方法,把目标网站域名解析到错误的IP地址从而实现用户无法访问目标网站的目的或者蓄意或恶意要求用户访问指 ...

  4. HashMap的实现原理及hash冲突(碰撞)解决方法

    HashMap 采用一种所谓的"Hash 算法"来决定每个元素的存储位置.当程序执行 map.put(String,Obect)方法 时,系统将调用String的 hashCode ...

  5. java源码系列:HashMap底层存储原理详解——5、技术本质-原理过程-算法-取模会带来一个什么问题?什么是哈希冲突?为什么要用链表?

    目录 取模会带来一个什么问题? 演示什么是哈希冲突(哈希碰撞)? 为什么要用链表? 其他--布隆过滤器 取模会带来一个什么问题? 好,那同学们这样他能达到一个目的,但是呢,它也会带来的一个问题,那它会 ...

  6. 高级数据结构与算法 | 哈希 :哈希冲突、负载因子、哈希函数、哈希表、哈希桶

    文章目录 哈希 哈希函数 常见的哈希函数 字符串哈希函数 哈希冲突 闭散列的解决方法 开散列的解决方法 负载因子以及增容 对于闭散列 对于开散列结构 具体实现 哈希表(闭散列) 插入 查找 删除 完整 ...

  7. 隐马尔可夫(HMM)、前/后向算法、Viterbi算法

    HMM的模型  图1 如上图所示,白色那一行描述由一个隐藏的马尔科夫链生成不可观测的状态随机序列,蓝紫色那一行是各个状态生成可观测的随机序列 话说,上面也是个贝叶斯网络,而贝叶斯网络中有这么一种,如下 ...

  8. 隐马尔可夫(HMM)、前/后向算法、Viterbi算法 再次总结

    本总结是是个人为防止遗忘而作,不得转载和商用. 说明:此篇是作者对"隐马尔可夫模型"的第二次总结,因此可以算作对上次总结的查漏补缺以及更进一步的理解,所以很多在第一次总结中已经整理 ...

  9. 什么是哈希算法?什么是哈希冲突以及怎样解决哈希冲突?

    哈希(Hash)算法,即散列函数.它是一种单向密码体制,即它是一个从明文到密文的不可逆的映射,只有加密过程,没有解密过程.同时,哈希函数可以将任意长度的输入经过变化以后得到固定长度的输出.哈希函数的这 ...

最新文章

  1. 【swjtu】数字电路实验2_杨辉三角发生器
  2. 【计算理论】下推自动机 PDA ( 设计下推自动机 | 上下文无关语法 CFG 等价于 下推自动机 PDA )
  3. linux面试题中的简答题,Linux面试题(简答题部分)
  4. POJ1063 Flip and Shift
  5. php 控制器自动,ThinkPHP 新建控制器
  6. 你的密码已泄露!使用C#阻止弱密码
  7. 云网管—云上构建网络自动化体系
  8. Oracle发布Oracle数据库的官方Node.js驱动node-oracledb
  9. 服务器mysql占用_mysql占用服务器cpu过高的原因以及解决办法
  10. csp-s2020 T1儒略日
  11. 分子生物学知识点归纳
  12. 儿科常见疾病的中成药疗法
  13. Instant及LocalDateTime等使用方法
  14. MySQL 管理之道读书总结
  15. 自适应QP(Adaptive QP)
  16. maya模型展开UV
  17. 英语3500词(14/20)dynasty主题 (2022.1.26)
  18. python gca_Matplotlib入门-3-plt.gca( )挪动坐标轴
  19. 【文献翻译】Select-Storage: A New Oracle Design Pattern on Blockchain
  20. unity 3d原创制作射击游戏(全完整版+安卓apk编译)

热门文章

  1. 安装OpenCV时提示缺少boostdesc_bgm.i文件的问题解决方案
  2. Markovdecisionprocesses_Discretestochasticdynamicprogramming下载
  3. MYSQL二级表的管理_MySQL库和表的管理
  4. 知识图谱学习笔记-图操作
  5. 数组中两个字符串的最小距离
  6. pytorch 笔记:DataLoader 扩展:构造图片DataLoader
  7. R语言应用实战系列(四)-Apriori算法的相关内容(附案例源代码)
  8. Linux疑难杂症解决方案100篇(八)-文本处理工具与bash的特性
  9. 深度学习100例 | 第30天:TensorFlow2 实现动物识别(90类)MobileNetV2算法(内附源码与数据)
  10. Python入门100题 | 第030题