哈希表查找失败的平均查找长度_你还应该知道的哈希冲突解决策略

本文首发于 vivo互联网技术 微信公众号
链接:https://mp.weixin.qq.com/s/5vxYoeARG1nC7Z0xTYXELA
作者:Xuegui Chen

哈希是一种通过对数据进行压缩, 从而提高效率的一种解决方法,但由于哈希函数有限,数据增大等缘故,哈希冲突成为数据有效压缩的一个难题。本文主要介绍哈希冲突、解决方案,以及各种哈希冲突的解决策略上的优缺点。

一、哈希表概述

哈希表的哈希函数输入一个键,并向返回一个哈希表的索引。可能的键的集合很大,但是哈希函数值的集合只是表的大小。

哈希函数的其他用途包括密码系统、消息摘要系统、数字签名系统,为了使这些应用程序按预期工作,冲突的概率必须非常低,因此需要一个具有非常大的可能值集合的散列函数。

密码系统:给定用户密码,操作系统计算其散列,并将其与存储在文件中的该用户的散列进行比较。(不要让密码很容易被猜出散列到相同的值)。

消息摘要系统:给定重要消息,计算其散列,并将其与消息本身分开发布。希望检查消息有效性的读者也可以使用相同的算法计算其散列,并与发布的散列进行比较。(不要希望伪造消息很容易,仍然得到相同的散列)。

这些应用的流行哈希函数算法有:

  • md5 : 2^128个值(找一个冲突键,需要哈希大约2 ^ 64个值)
  • sha-1:2^160个值(找一个冲突键,需要大约2^80个值)

二、哈希冲突

来看一个简单的实例吧,假设采用hash函数:H(K) = K mod M,插入这些值:217、701、19、30、145

H(K) = 217 % 7 = 0
H(K) = 701 % 7 = 1
H(K) = 19 % 7 = 2
H(K) = 30 % 7 = 2
H(K) = 145 % 7 = 5

d0e4f04dcec276b5e045b4672a5b4542.png

上面实例很明显 19 和 30 就发生冲突了。

三、冲突解决策略

除非您要进行“完美的散列”,否则必须具有冲突解决策略,才能处理表中的冲突。
同时,该策略必须允许查找,插入和删除正确运行的操作!

冲突解决技术可以分为两类:开散列方法( open hashing,也称为拉链法,separate chaining )和闭散列方法( closed hashing,也称为开地址方法,open addressing )。这两种方法的不同之处在于:开散列法把发生冲突的关键码存储在散列表主表之外,而闭散列法把发生冲突的关键码存储在表中另一个槽内。

下面介绍业内比较流行的hash冲突解决策略:

  • 线性探测(Linear probing)
  • 双重哈希(Double hashing)
  • 随机散列(Random hashing)
  • 分离链接(Separate chaining)

上面线性探测、双重哈希、随机散列都是闭散列法,而分离链接则是开散列法。

1、线性探测(Linear probing)

插入一个值

使用散列函数H(K)在大小为M的表中插入密钥K时:

  1. 设置 indx = H(K)
  2. 如果表位置indx已经包含密钥,则无需插入它。Over
  3. 否则,如果表位置indx为空,则在其中插入键。Over
  4. 其他碰撞。设置 indx =(indx + 1)mod M.
  5. 如果 indx == H(K),则表已满!就只能做哈希表的扩容了

因此,线性探测基本上是在发生碰撞时对空槽进行线性搜索。

优点:易于实施;总是找到一个位置(如果有);当表不是很满时,平均情况下的性能非常好。

缺点:表的相邻插槽中会形成“集群”或“集群”键;当这些簇填满整个阵列的大部分时,性能会严重下降,因为探针序列执行的工作实际上是对大部分阵列的穷举搜索。

简单例子

如哈希表大小M = 7, 哈希函数:H(K) = K mod M。插入这些值:701, 145, 217, 19, 13, 749

H(K) = 701 % 7 = 1
H(K) = 145 % 7 = 5
H(K) = 217 % 7 = 0
H(K) = 19 % 7 = 2
H(K) = 13 % 7 = 1(冲突) --> 2(已经有值) --> 3(插入位置3)
H(K) = 749 % 7 = 2(冲突) --> 3(已经有值) --> 4(插入位置4)

可见,如果哈希表如果不是很大,随着数据插入,冲突也会组件发生,探针遍历次数将会逐渐变低,检索过程也就成为穷举。

检索一个值

如果使用线性探测将键插入表中,则线性探测将找到它们!

当使用散列函数 H(K)在大小为N的表中搜索键K时:

  1. 设置 indx = H(K)
  2. 如果表位置indx包含键,则返回FOUND。
  3. 否则,如果表位置 indx 为空,则返回NOT FOUND。
  4. 否则设置 indx =(indx + 1)modM。
  5. 如果 indx == H(K),则返回NOT FOUND。就只能做哈希表的扩容了

问题:如何从使用线性探测的表中删除键?

能否进行“延迟删除”,而只是将已删除密钥的插槽标记为空?

很明显,在线性探测很难做到,如果把位置置为空,那么如果后面的值也是哈希冲突,线性探测插入,则再也无法遍历这些值了。

2、双重哈希(Double hashing)

线性探测冲突解决方案会导致表中出现簇,因为如果两个键发生碰撞,则探测到的下一个位置对于这两个键都是相同的。

双重哈希的思想:使偏移到下一个探测到的位置取决于键值,因此对于不同的键可以不同。

需要引入第二个哈希函数 H 2(K),用作探测序列中的偏移量(将线性探测视为 H 2(K)== 1 的双重哈希)。

对于大小为 M 的哈希表,H 2(K)的值应在 1到M-1 的范围内;如果M为质数,则一个常见选择是 H2(K)= 1 +((K / M)mod(M-1))。

然后,用于双哈希的插入算法为:

  1. 设置 indx = H(K); offset = H 2(K)
  2. 如果表位置indx已经包含密钥,则无需插入它。Over
  3. 否则,如果表位置 indx 为空,则在其中插入键。Over
  4. 其他碰撞。设置 indx =(indx + offset)mod M.
  5. 如果 indx == H(K),则表已满!就只能做哈希表的扩容了

哈希表为质数情况,双重hash在实践中非常有效

双重 Hash 也见:https://blog.csdn.net/chenxuegui1234/article/details/103454285

3、随机散列(Random hashing)

与双重哈希一样,随机哈希通过使探测序列取决于密钥来避免聚类。

使用随机散列时,探测序列是由密钥播种的伪随机数生成器的输出生成的(可能与另一个种子组件一起使用,该组件对于每个键都是相同的,但是对于不同的表是不同的)。

然后,用于随机哈希的插入算法为:

  1. 创建以 K 为种子的 RNG。设置indx = RNG.next() mod M。
  2. 如果表位置 indx 已经包含密钥,则无需插入它。Over
  3. 否则,如果表位置 indx 为空,则在其中插入键。Over
  4. 其他碰撞。设置 indx = RNG.next() mod M.
  5. 如果已探测所有M个位置,则放弃。就只能做哈希表的扩容了。

随机散列很容易分析,但是由于随机数生成的“费用”,它并不经常使用。双重哈希在实践中还是经常被使用。

4、分离链接(Separate chaining)

在具有哈希函数 H(K)的表中插入键K时

  1. 设置 indx = H(K)
  2. 将关键字插入到以 indx 为标题的链接列表中。(首先搜索列表,以避免重复。)

在具有哈希函数H(K)的表中搜索键K时

  1. 设置 indx = H(K)
  2. 使用线性搜索在以 indx 为标题的链表中搜索关键字。

使用哈希函数 H(K)删除表中的键K时

  1. 设置 indx = H(K)
  2. 删除链接列表中以 indx 为标题的键

优点:随着条目数量的增加,平均案例性能保持良好。甚至超过M;删除比开放地址更容易实现。

缺点:需要动态数据,除数据外还需要存储指针,本地性较差,导致缓存性能较差。

很明显,Java7 的 HashMap 就是一种分裂链接的实现方式。

分离链哈希分析

请记住表的填充程度的负载系数度量:α = N / M。

其中M是表格的大小,并且 N 是表中已插入的键数。

通过单独的链接,可以使 α> 1 给定负载因子α,我们想知道在最佳,平均和最差情况下的时间成本。

成功找到

新键插入和查找失败(这些相同),最好的情况是O(1),最坏的情况是O(N)。让我们分析平均情况

分裂链接的平均成本

假设负载系数为 α = N / M 的表
在M个链接列表中总共分配了N个项目(其中一些可能为空),因此每个链接列表的平均项目数为:

  • 如果查找/插入失败,则必须穷举搜索表中的链表之一,并且表中链表的平均长度为α。因此,使用单独链接进行插入或不成功查找的比较平均次数为

bbd8e912122f73cf55ceeeb31e6bff7a.png
  • 成功查找后,将搜索包含目标密钥的链接列表。除目标密钥外,该列表中平均还有(N-1)/ M个密钥;在找到目标之前,将平均搜索其中一半。因此,使用单独链接成功找到的比较平均次数为

38fe8868fda4e5a3fd28a12f0e1e59e6.png

当α<1时,它们分别小于1和1.5。并且即使当α超过1时,它们仍然是O(1),与N无关。

四、开散列方法 VS 闭散列方法

如果将键保留为哈希表本身中的条目,则可以使用线性探测,双重和随机哈希... 这样做称为“开放式寻址”,也称为“封闭式哈希”。

另一个想法:哈希表中的条目只是指向链表(“链”)头部的指针;链接列表的元素包含键... 这称为“单独链接”,也称为“开放式哈希”。

通过单独的链接,冲突解决变得容易:只要在其链表中插入一个键,就可以将其插入(为此,可以使用比链表更高级的数据结构;但是正如我们将看到的,链表在一般情况下效果很好)。

让下面我们看一下这些策略的时间成本。

开放式地址哈希分析

分析哈希表“查找”或“插入”性能时,一个有用的参数是负载系数 α = N / M。

其中 M 是表格的大小,并且 N 是表中已插入的键数负载系数是表满度的一种度量。

给定负载因子 α ,我们想知道在最佳,平均和最差情况下的时间成本。

成功找到
对所有键,最好的情况是O(1),最坏的情况是O(N),新键插入和查找失败(这些相同),所以让我们分析平均情况。
我们将给出随机哈希和线性探测的结果。实际上,双重哈希类似于随机哈希;

平均不成功的查找/插入成本

假定负载系数为α= N / M的表。考虑随机散列,因此聚类不是问题。每个探针位置是随机且独立生成的对于每个探针,找到空位置的可能性为(1-α)。查找空位置将停止查找或插入,这是一个伯努利过程,成功概率为(1-α)。该过程的预期一阶到达时间为 1 /(1-α)。所以:

使用随机哈希进行插入或不成功查找的探针的平均数量为

a9699234401c4650bfadb775cd67a696.png

使用线性探测时,探头的位置不是独立的。团簇形成,当负载系数高时会导致较长的探针序列。可以证明,用于线性探测的插入或未成功发现的探针的平均数量约为

9c33738311cef3a65ab1242705e512ef.png

当 α 接近1时,这些平均案例时间成本很差,仅受M限制;但当 α 等于或小于7.75(与M无关)时,效果还不错(分别为4和8.5)

平均成功查找成本

假定负载系数为 α= N / M 的表。考虑随机散列,因此聚类不是问题。每个探针位置是随机且独立生成的。

对于表中的键,成功找到它所需的探针数等于将其插入表中时所采用的探针数。每个新密钥的插入都会增加负载系数,从0开始到α。

因此,通过随机散列成功发现的探测器的平均数量为

3b24ea5314a3c5941dc138e6249a979a.png

通过线性探测,会形成簇,从而导致更长的探针序列。可以证明,通过线性探测成功发现的平均探针数为

d95c302fc3f97612f4405436df769e24.png

当α接近1时,这些平均案例时间成本很差,仅受M限制;但当α等于或小于7.75时好(分别为1.8和2.5),与M无关。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/539022.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python 正则替换_5分钟速览Python正则表达式常用函数!五分钟就掌握它!

导读&#xff1a;正则表达式是处理字符串类型的"核武器"&#xff0c;不仅速度快&#xff0c;而且功能强大。本文不过多展开正则表达式相关语法&#xff0c;仅简要介绍python中正则表达式常用函数及其使用方法&#xff0c;以作快速查询浏览。01 Re概览Re模块是python的…

oracle 分组_大数据分组怎样才会更快

分组是数据库的常见运算&#xff0c;无论数据如何准备&#xff0c;通常都需要将所有数据遍历。建立索引这时是不起作用的&#xff0c;存储格式才是决定遍历效率的主要因素。数据库中数据的存放虽然是二进制格式的&#xff0c;但普遍IO性能差&#xff0c;库内遍历快&#xff0c;…

java下拉树_参数模板中下拉树级联下拉数据集查询

背景说明在参数表单的制作中会遇到各种各样的需求&#xff0c;如为了方便参数的输入&#xff0c;需要将输入框设计成树状&#xff1b;若参数模板中有两个输入框&#xff0c;每个输入框对应的参数有某种关系&#xff0c;前一个输入框输入参数后&#xff0c;后一个输入框自动关联…

java8 list 行转列_太赞了,Intellij IDEA 竟然把 Java8 的数据流问题这么完美的解决掉了!

使用 IntelliJ IDEA 来帮忙构建你自己的实时模板连接分组《Java 2019 超神之路》《Dubbo 实现原理与源码解析 —— 精品合集》《Spring 实现原理与源码解析 —— 精品合集》《MyBatis 实现原理与源码解析 —— 精品合集》《Spring MVC 实现原理与源码解析 —— 精品合集》《Spr…

beautifulsoup爬取网页中的表格_用 Python 爬取网页

来自公众号&#xff1a;优达学城Udacity作者&#xff1a;Kerry Parker编译&#xff1a;欧剃作为数据科学家的第一个任务&#xff0c;就是做网页爬取。那时候&#xff0c;我对使用代码从网站上获取数据这项技术完全一无所知&#xff0c;它偏偏又是最有逻辑性并且最容易获得的数据…

pyecharts添加文字_超燃的文字云效果,用Python就能轻松get!

本文转载自公众号&#xff1a;数据森麟(ID&#xff1a;shujusenlin)作者&#xff1a;叶庭云链接&#xff1a;https://blog.csdn.net/fyfugoyfa/ 01 / 词云图词云图是一种用来展现高频关键词的可视化表达&#xff0c;通过文字、色彩、图形的搭配&#xff0c;产生有冲击力地视觉效…

mysql 创建视图 主键_MySQL数据库基础操作命令,本文助你更上一层楼!

今天介绍的是关于Mysql数据库一些操作的基础命令用户与权限创建用户mysql>create user test identified by BaC321#; 修改密码5.5版本及以前的命令mysql>set password for testpassowrd(!1A2#3); 5.6及以上命令mysql>update mysql.user set authentication_stringpass…

mysql 聚合函数 怎么用在条件里_MySql 中聚合函数增加条件表达式的方法

Mysql 与聚合函数在一起时候where条件和having条件的过滤时机where 在聚合之前过滤当一个查询包含了聚合函数及where条件&#xff0c;像这样的情况select max(cid) from t where t.id<999这时候会先进行过滤&#xff0c;然后再聚合。先过滤出ID《999的记录&#xff0c;再查找…

drbd(三):drbd的状态说明

1.几种获取状态信息的方法 drbd有很多获取信息的方式。在drbd84和之前的版本&#xff0c;大多都使用cat /proc/drbd来获取信息&#xff0c;多数情况下&#xff0c;这个文件展示的信息对于管理和维护drbd来说已经足够。 例如以下是drbd84上两个volume的节点状态信息&#xff1a;…

python QTreeWidgetItem下面有几个子tree_python-nlp ch1笔记:nlp的基础应用、高级应用、python优势、nltk环境搭建...

本帖是对(印度)Jalaj Thanaki作品《python自然语言处理》的翻译、缩减及改编~nlp的基础应用NLP是AI的子分支&#xff0c;其相关概念可以用于以下专家系统中&#xff1a;语音识别系统问答系统机器翻译文本摘要情感分析基于模板的聊天机器人文本分类主题分割nlp的高级应用理解自然…

C#使用ListView更新数据出现闪烁解决办法

C#使用ListView更新数据出现闪烁解决办法 在使用vs自动控件ListView控件时候&#xff0c;更新里面的部分代码时候出现闪烁的情况 如图&#xff1a; 解决以后&#xff1a; 解决办法使用双缓冲&#xff1a;添加新类继承ListView 对其重写 1 public class DoubleBufferListView : …

python语音识别的第三方库_python标准库+内置函数+第三方库: 7.音频处理

python标准库内置函数第三方库 欲善其事&#xff0c;必先利其器 这其器必是python的标准库内置函数&#xff0c;话说许多第三方库&#xff0c; 也是对标准库的使用&#xff0c;进行封装&#xff0c;使得使用起来更方便。 这些库以使用场景来分类: 7、音频处理 音频处理主要适用…

python 多线程并行 矩阵乘法_python实现简单的并行矩阵乘法

python实现简单的并行矩阵乘法python实现简单的并行矩阵乘法本文采用的矩阵乘法方式是利用一个矩阵的行和二个矩阵的列相乘时不会互相影响。假设A(m,n)表示矩阵的m行&#xff0c;n列。那么C(m,m)A(m,n) * B(n,m) &#xff1a;计算C矩阵时候分解成&#xff1a;process-1&#xf…

报错 classes 拒绝访问_3种方式“移除”快速访问;为什么移除?你懂的...

Windows 10 在文件资源管理器中引入了"快速访问"这个功能&#xff0c;每当打开文件资源管理器窗口时&#xff0c;您都会看到常用文件夹和最近访问的文件的列表&#xff0c;这个功能虽然方便了日常使用&#xff0c;可能会提高工作效率&#xff0c;但是如果是公司的电脑…

java set是重复_java算法题,set内出现重复元素

题目将数字 1…9 填入一个33 的九宫格中&#xff0c;使得格子中每一横行和的值全部相等&#xff0c;每一竖列和的值全部相等。请你计算有多少种填数字的方案。这个是计蒜客上面的一个模拟题&#xff0c;我采用暴力。public class _3 {/** 将数字 1…9 填入一个33 的九宫格中&am…

python中把输出结果写到一个文件中_Python3.6笔记之将程序运行结果输出到文件的方法...

Python3.6笔记之将程序运行结果输出到文件的方法 更新时间&#xff1a;2018年04月22日 14:27:32 投稿&#xff1a;jingxian 下面小编就为大家分享一篇Python3.6笔记之将程序运行结果输出到文件的方法&#xff0c;具有很好的参考价值&#xff0c;希望对大家有所帮助。一起跟随小…

ReadWriteLock读写文件

概述 ReadWriteLock是一个接口&#xff0c;在它里面只定义了两个方法&#xff1a;一个读的锁和一个写的锁。 读的锁&#xff1a;A线程获取了读的锁&#xff0c;那么B线程也可以获取读的锁。 写的锁&#xff1a;A线程获取了写的锁&#xff0c;那么B线程不能获取读也不能获取写…

Java中的Runnable、Callable、Future、FutureTask的区别与示例

原文地址&#xff1a;http://blog.csdn.net/bboyfeiyu/article/details/24851847 --------------------------------------------------------- Java中存在Runnable、Callable、Future、FutureTask这几个与线程相关的类或者接口&#xff0c;在Java中也是比较重要的几个概念&am…

sql count为空时显示0_C0010负坐标显示为正数+红色0值参考线

小伙伴们早上好啊&#xff01;今天继续为大家分享柱形图的美化技巧。希望大家认真阅读Excel文件和教程&#xff0c;有的图表看起来简单&#xff0c;实际上在细节处理上用了很多技巧&#xff0c;大家要多多体会。C0010-负坐标显示为正数红色0值参考线效果图图表概述本图可以用来…

配置IISExpress允许外部访问

配置IISExpress允许外部访问 1.找到IISExpress的配置文件&#xff0c;位于 <文档>/IISExpress/config文件夹下&#xff0c;打开applicationhost.config&#xff0c;找到如下代码&#xff1a;<site name"WebSite1" id"1" serverAutoStart"tru…