7    VisuAlgo.net / /hashtable Login LP QP DH SC
示例模式 ▿

>

>
go to beginning previous frame pause play next frame go to end
哈希表是一种将键映射到值的数据结构。它用哈希方程来将键映射到小范围的指数(一般为[0..哈希表大小-1])。 两个键冲突为 同样指数的几率相对较高并且每次潜在的冲突需要被解决才能维持数据完整。在这个可视化中有一些解决冲突的策略会被高亮: 开放寻址法(线性探测,二次探测 和 双倍散列)以及 分离连接法(即将上线)。

Remarks: By default, we show e-Lecture Mode for first time (or non logged-in) visitor.
Please login if you are a repeated visitor or register for an (optional) free account first.

X Esc
下一个 PgDn

散列是一种算法(通过散列函数),将大型可变长度数据集(称为键,不一定是整数)映射为固定长度的较小整数数据集。
哈希表是一种数据结构,它使用哈希函数有效地将键映射到值(表或地图ADT),以便进行高效的搜索/检索,插入和/或删除。
散列表广泛应用于多种计算机软件中,特别是关联数组,数据库索引,缓存和集合。
在这个电子讲座中,我们将深入讨论表ADT,哈希表的基本概念,讨论哈希函数,然后再讨论哈希表数据结构本身的细节。


Pro-tip: Since you are not logged-in, you may be a first time visitor who are not aware of the following keyboard shortcuts to navigate this e-Lecture mode: [PageDown] to advance to the next slide, [PageUp] to go back to the previous slide, [Esc] to toggle between this e-Lecture mode and exploration mode.

X Esc
上一个 PgUp
下一个 PgDn

ADT表必须至少支持以下三种操作,并且尽可能高效:

  1. 搜索(v) - 确定v是否存在于ADT中,
  2. 插入(v) - 将v插入ADT,
  3. 删除(v) - 从ADT中删除v。

哈希表是这个表ADT的一个可能的好实现(另一个是这个)。


PS1:对于Table ADT的两个较弱的实现,您可以单击相应的链接:未排序数组排序数组来阅读详细讨论。


PS2:在现场课程中,您可能想要比较Table ADT和List ADT的要求。


Another pro-tip: We designed this visualization and this e-Lecture mode to look good on 1366x768 resolution or larger (typical modern laptop resolution in 2017). We recommend using Google Chrome to access VisuAlgo. Go to full screen mode (F11) to enjoy this setup. However, you can use zoom-in (Ctrl +) or zoom-out (Ctrl -) to calibrate this.

X Esc
上一个 PgUp
下一个 PgDn

当整数键的范围很小时,例如 [0..M-1],我们可以使用大小为M的初始空(Boolean)数组A,并直接实现以下表ADT操作:

  1. 搜索(v):检查A [v]是true(填充)还是false(空),
  2. 插入(v):将A [v]设置为true(填充),
  3. 移除(v):将A [v]设置为false(空白)。

就是这样,我们使用小整数键本身来确定数组A中的地址,因此称为直接寻址。 很明显,所有三种主要的ADT操作都是O(1)

X Esc
上一个 PgUp
下一个 PgDn

在新加坡(截至2018年3月),公交路线编号是从[2,990]。
当前并不是所有的[2..990]之间的整数,例如, 没有公交线路989 - 搜索(989)应该返回错误。 我们可以引入新的公共汽车路线 x,即插入(x)或现有的公共汽车路线 y 可以不连续,即移除(y)。
由于可能的公交路线的范围很小,为了记录数据是否存在公交线路号码,我们可以使用具有1 000大小的布尔数组(Boolean array)的DAT。

讨论:在现实生活中,我们可以讨论为什么我们使用1 000而不是990(或991)。

X Esc
上一个 PgUp
下一个 PgDn

请注意,我们总是可以添加数值,而不是使用boolean数组来记录密钥的存在。
例如,我们可以使用一个关联的字符串数组A来代替一个公交路线号码到它的运营商名称,例如,

A[2] = "Go-Ahead Singapore",
A[10] = "SBS Transit",
A[183] = "Tower Transit Singapore",
A[188] = "SMRT Buses", etc.

讨论:你能想到其他一些现实生活中的DAT示例吗?

X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn
键必须是(或可以轻松映射到)非负整数值。 基本的DAT在前面两张幻灯片的例子的完整版本中存在问题,因为在新加坡实际上有公交路线号的变化,例如, 96B,151A,NR10等
键的范围必须很小。 如果我们有(非常)大范围的话,内存使用量会(非常的)很大。
键必须密集,即键值中没有太多空白。 否则DAT将包含太多的空单元。
我们将用哈希来克服这些限制。
X Esc
上一个 PgUp
下一个 PgDn

使用散列,我们可以:

  1. 将(一些)非整数键映射成整数键,
  2. 将大整数映射成较小的整数,
  3. 影响哈希表的密度或负载因子α= N / M,其中N是键的数量,M是哈希表的大小。
X Esc
上一个 PgUp
下一个 PgDn
举个例子,我们有N = 400个新加坡电话号码(新加坡电话号码有8位数字,所以在新加坡可能有10 ^ 8 = 100M的电话号码)。
我们可以使用以下简单的哈希函数h(v)= v%997来代替使用DAT并使用大小为M = 1亿的巨大的数组。

这样,我们将8位电话号码6675 23786874 4483分别映射到最多3位数字h(6675 2378)= 237h(6874 4483)= 336。 因此,我们只需要准备大小为M = 997(或1000)的数组而不是M = 1亿。
X Esc
上一个 PgUp
下一个 PgDn
使用散列,我们现在可以使用整数数组(而不是布尔数组)执行下面的表ADT操作,如下所示:
  1. 搜索(v):检查A [h(v)]!= -1(假设 v≥0,我们对空单元使用-1)
  2. 插入(v):设置A [h(v)] = v(我们把 v 散列到h(v)中,所以我们需要以某种方式记录键 v),
  3. 删除(v):设置A [h(v)] = -1 --- 将进一步阐述。
X Esc
上一个 PgUp
下一个 PgDn

如果我们有映射到卫星数据的键,并且我们也想记录原始键,我们可以使用如下一对(整数,卫星数据类型)数组实现哈希表:

  1. 搜索(v):返回A [h(v)],它是一对v,卫星数据),可能是空的,
  2. 插入(v,卫星数据):设置A [h(v)] =对(v,卫星数据)
  3. 删除(v):设置A [h(v)] =(空对) - 将进一步详细阐述。

但是,现在你应该注意到有些地方是不完整的......

X Esc
上一个 PgUp
下一个 PgDn

散列函数可能且很可能将不同的键(整数或不是)映射到同一个整数槽中,即多对一映射而不是一对一映射。
例如,早些时候三张幻灯片中的h(6675 2378)= 237,如果我们想插入另一个电话号码6675 4372,我们也会遇到问题,因为h(6675 4372)= 237
这种情况称为冲突,即两个(或更多)键具有相同的散列值

X Esc
上一个 PgUp
下一个 PgDn
生日(冯米塞斯)悖论问:'在一个人与其他人共享同一天生日(碰撞,两个键被散列的概率)之前,在一个365个座位(单元格)的房间(散列表)中必须有多少人(键数量) ,忽略闰年(即所有年份都有365天),变得> 50%(即更可能)?
Reveal这个答案对我们中的一些人来说可能令人惊讶。
我们来做一些计算吧。
X Esc
上一个 PgUp
下一个 PgDn

假设Q(n)是房间中n个人唯一生日的概率。
Q(n)= 365/365×364/365×363/365×...×(365-n + 1)/ 365,
即第一人的生日可以是365天中的任何一天,第二人的生日可以是除第一人的生日之外的任何365天,等等。
P(n)为房间中 n 个人的相同生日(碰撞)的概率。P(n)= 1-Q(n)
我们计算P(23) = 0.507> 0.5(50%)。
因此,我们只需要在365人的座位(单元格)的房间(哈希表)中为23人(少量钥匙)发生(超过)50%的机会碰撞事件(该房间中两个不同人的生日 是365天/插槽之一)。

X Esc
上一个 PgUp
下一个 PgDn

问题1:我们已经看到了一个简单的散列函数,如电话号码示例中使用的h(v)= v%997,它将大范围的整数键映射到整数键的较小范围内,但非整数键的情况如何? 如何有效地做这样的散列?
问题2:我们已经看到,通过将大范围散列或映射到更小范围,很可能会发生碰撞。 如何处理它们?

X Esc
上一个 PgUp
下一个 PgDn
如何用这些理想的属性创建一个好的散列函数?
  1. 快速计算,即在O(1)中,
  2. 尽可能使用最小插槽/散列表的大小M,
  3. 尽可能均匀地将键分散到不同的基地址∈[0..M-1],
  4. 尽可能减少碰撞。
X Esc
上一个 PgUp
下一个 PgDn
假设我们有一个大小为 M 的散列表,其中键用于标识卫星数据,并且使用特定的散列函数来计算散列值。
使用散列函数从键v计算键v的散列值/散列码以获得范围从0到M-1的整数。 该散列值用作卫星数据的散列表条目的基址/归位索引/地址。
X Esc
上一个 PgUp
下一个 PgDn


X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn

在讨论现实之前,让我们讨论理想的情况:完美的散列函数
完美的散列函数是键和散列值之间的一对一映射,即根本不存在冲突。 如果事先知道所有的键是可能的。 例如,编译器/解释器搜索保留关键字。 但是,这种情况很少见。
当表格大小与提供的关键字数量相同时,实现最小的完美散列函数。 这种情况更为罕见。
如果你有兴趣,你可以探索GNU gperf,这是一个用C ++编写的免费可用的完美哈希函数生成器,可以从用户提供的关键字列表中自动构建完美的函数(C ++程序)。

X Esc
上一个 PgUp
下一个 PgDn
人们已经尝试过各种方法,尽可能均匀地将大范围的整数散列到更小范围的整数中。 在这个电子讲座中,我们直接跳到最好的和最受欢迎的版本之一:h(v)= v%M,即将v 映射到大小为M个时隙的散列表中。 (%)是一个模运算符,给出了除法后的余数。 这显然很快,即O(1)假设v不超过自然整数数据类型限制。
哈希表大小M被设置为不接近2的幂的相当大的素数,大约比将在哈希表中使用的期望的键的数量N大2倍以上。 这样,负载因子α= N / M <0.5 稍后我们将看到具有低负载因子,从而牺牲空闲空间,有助于提高哈希表性能。
讨论:如果我们将M设置为10(十进制)的幂或2(幂的二进制),那该怎么办?
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn
人们也尝试过各种方法,尽可能均匀地将字符串散列到小范围的整数中。 在这个电子讲座中,我们直接跳到最好和最流行的版本之一,如下所示:

int hash_function(string v){//假设1:v仅使用['A'..'Z']
   int sum = 0; //假设2:v是一个短字符串
   for(auto&c:v)//对于v中的每个字符c
     sum =((sum * 26)%M +(c - 'A' + 1))%M; // M是表格大小
   return sum;
}
讨论:在现实生活中,讨论上面散列函数的组成部分,例如 为什么循环遍历所有字符?,会比O(1)慢吗?为什么要用26来乘?,如果字符串v使用的不仅仅是大写字符?,等等
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn
选择分别的页眉以在不同使用模式之间切换:分离连接法或者开放寻址法(线性探测,二次探测 和 双倍散列)
设:
HT_size = 现在哈希表的大小
基础 = (值%HT_size) 
步骤 =  现在探测的步骤 
第二级 = 1+值%(HT_size-2) ( 防止为0 ), 则: 

线性探测: i=(基础+步骤*1)%HT_size,
二次探测: i=(基础+步骤*步骤)%HT_size, 以及
双倍散列: i=(基础+步骤*第二级)%HT_size

X Esc
上一个 PgUp
下一个 PgDn
在此可视化中讨论了三种开放寻址(OA)冲突解决技术:线性探测(LP),二次探测(QP)和双散列(DH)。
要在三种模式之间切换,请点击相应的标题。
假设:M = HT.length =当前散列表大小,base =(key%HT.length),step =当前探测步骤,secondary = smaller_prime - key%smaller_prime(避免零 - 很快就会阐述)我们很快就会看到 三种模式的探测序列为:线性探测:i =(基础+步骤* 1)%M,二次探测:i =(基础+步骤*步骤)%M和双散列:i =(基础+步骤 *次要)%M.
X Esc
上一个 PgUp
下一个 PgDn

分离链接法(SC)冲突解决技术很简单。 我们使用 M 个副本数据结构副本,通常是双向链接列表。 如果两个键 a b 都具有相同的散列值 i,则两个键都将被附加到双链表 i 的(前/后)(在该可视化中,我们借助尾指针,用O(1)的时间将它附加到后面)。 就是这样,键(keys)将被插入的地方完全依赖于散列函数本身,因此我们也称分离链接法为封闭寻址冲突解决技术。

如果我们使用分离链接法,负载因子α= N / M描述了M个列表的平均长度,它将决定搜索的性能(v),因为我们可能需要平均探索α个元素。 由于删除(v) - 也需要搜索(v),其性能与搜索(v)相似。 插入(v)显然是O(1)。
如果我们可以将α限制为一个小常量,则使用分离链接法的所有搜索(v),插入(v)和删除(v)操作都将为O(1)。

X Esc
上一个 PgUp
下一个 PgDn
查看上面的哈希表的可视化。
在这个可视化中,我们防止重复键的插入。
由于屏幕大小限制,散列表尺寸被限制为M=19
哈希表在水平方向上可视化为一个数组,其中指数0放置在最左边,指数M-1放置在最右边,但是当我们可视化开放寻址 vs 分离链接冲突解决方法时,细节会有不同。
X Esc
上一个 PgUp
下一个 PgDn
本可视化中讨论了三种开放寻址冲突解决技术:线性探测(LP),二次探测(QP)和双散列(DH)。
对于所有三种技术,每个哈希表单元格都显示为顶点标签显示的单元格值为[0..99]的顶点。 在不失一般性的情况下,我们不会在该可视化中显示任何卫星数据,因为我们只关注按键的排列。 我们保留值-1以指示'EMPTY cell'(可视化为空白顶点),-2指示'DELETED cell'(可视化为具有缩写标签“DEL”的顶点)。 单元格索引范围从[0..M-1]在每个顶点下方显示为红色标签
X Esc
上一个 PgUp
下一个 PgDn

对于分离连接法(SC)冲突解决方法,第一行包含M双向链表M“H”(头)指针。
然后,每个双向链表我包含按任意顺序散列到 i 中的所有密钥。 在数学上,所有可以表示为i(mod M)的键都被散列到DLL i 中。 再次,我们不在此可视化中存储任何卫星数据。

X Esc
上一个 PgUp
下一个 PgDn
在线性探测冲突解决方法中,每当发生冲突时,我们会一次扫描一个索引,以查找下一个空/被删除的插槽(当我们到达最后一个插槽时再重新环绕)。
例如,我们假设我们以表格大小M = HT.length = 7的空的散列表 HT开始,如上所示,使用索引0到 M-1 = 7-1 = 6。请注意,7是素数。 (主)散列函数很简单,h(v)= v%M
此演练将向您展示使用线性探测作为冲突解决技术时Insert(v),Search(v)和Remove(v)操作所采取的步骤。
X Esc
上一个 PgUp
下一个 PgDn

现在单击Insert([18,14,21]) - 一个命令中有三个单独的插入。
回顾(点击上面的按钮后显示)。
形式上,我们将线性探测指数 i描述为i =(base + step * 1)%M,其中base是键v的(主)散列值,即h(v)step是从1开始的线性探测步骤。
提示:为了快速精确地计算(小)整数VM,我们简单地用M≤V的最大倍数来减去V,例如, 18%7 = 18-14 = 4,因为14是7的最大倍数并且≤18。

X Esc
上一个 PgUp
下一个 PgDn
现在点击Insert([1,35])(在上一张幻灯片中插入的前三个值的顶部)。
回顾(在点击上面的按钮后显示)
X Esc
上一个 PgUp
下一个 PgDn
现在我们说明使用线性探测作为碰撞解析技术的Search(v)操作。 所采取的步骤与 Insert(v)操作非常相似,即我们从(主)散列键值开始,检查是否已找到v,否则我们一次向前移动一个索引(如果需要,则进行包装)并重新检查 当我们遇到一个空单元时,我们就停止,这意味着v完全不在哈希表中(因为之前的Insert(v)操作会将v放在那里)。

现在点击Search(35) - 你应该看到探测序列[0,1,2,3(找到键35)]。

现在单击Search(8) - [1,2,3,4,5(空单元格,因此在哈希表中找不到键8)]。
X Esc
上一个 PgUp
下一个 PgDn
现在我们来讨论移除(v)操作。
如果我们直接设置HT [i] = EMPTY单元,其中i是包含v的索引(在必要时进行线性探测之后),您是否意识到我们会导致问题? 为什么?

提示:查看以前的三张幻灯片,了解插入(v)和搜索(v)的行为。
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn
现在让我们看看完整的删除(v)。 如果我们在索引 i 处找到 v(必要时在线性探测之后),我们必须设置 HT [i] = DELETED(在此可视化中缩写为DEL),其中DEL是一个特殊符号(通常你只能在你的应用程序种没有使用过的使用一个特殊符号)指示该单元格可以在将来的搜索(v)需要时绕过,但可以被将来的插入(w)覆盖。 这种策略被称为懒惰删除。
现在点击Remove(21) - [0,1(找到关键21,我们设置H [1] = DEL)]。
然后请继续讨论下一张幻灯片。
X Esc
上一个 PgUp
下一个 PgDn
现在点击Search(35) - [0,1(绕过那个DELETED cell),2,3(找到键35)]。
想象一下,如果我们错误地设置H [1] = EMPTY,会发生什么。
X Esc
上一个 PgUp
下一个 PgDn
现在单击Insert(28) - 您应该看到探测序列[0,1(使用DEL符号找到一个单元格)],因此,实际上可以用新值覆盖,而不会影响将来搜索(v)的正确性。 因此,我们把28放在索引1中。
X Esc
上一个 PgUp
下一个 PgDn
虽然我们可以解决与线性探测的碰撞,但它不是最有效的方法。
我们将一个群集定义为连续占用时隙的群集。 覆盖键基地址的集群称为键的主群集。
现在请注意,线性探测可以创建大型主群集,这会增加搜索(v)/插入(v)/删除(v)操作在O(1)之外的运行时间。
参见上面的一个例子,M = 11,我们插入了全部为6(模11)的键,即当除以11时,所有键都有余数6.现在看看'慢'(BTN92)是如何。
X Esc
上一个 PgUp
下一个 PgDn
线性探测的探测序列可以正式描述如下:

  h(v)//基地址
(h(v)+ 1 * 1)%M //第一个探测步骤,如果发生碰撞
(h(v)+ 2 * 1)%M //第二次探测步骤,如果仍有碰撞
(h(v)+ 3 * 1)%M //第三次探测步骤,如果仍有冲突
...
(h(v)+ k * 1)%M //第k个探测步骤等...

在Insert(v)过程中,如果发生冲突,但在Hash Table中仍有一个空(或DEL)时隙,我们肯定会在最多M个线性探测步骤后找到它。 而当我们这样做时,冲突将得到解决,但是键 v 的主集群会因此而扩展,未来的哈希表操作也会变得更慢。
由于相邻(但先前不相连)的群集的兼并(或组合),主群集的大小可能非常大。
X Esc
上一个 PgUp
下一个 PgDn
为了减少主聚类,我们可以将探针序列修改为:

  h(v)//基地址
(h(v)+ 1 * 1)%M //第一个探测步骤,如果发生碰撞
(h(v)+ 2 * 2)%M //第2次探测步骤,如果仍有冲突
(h(v)+ 3 * 3)%M //第三次探测步骤,如果仍有冲突
...
(h(v)+ k * k)%M //第k个探测步骤等...
就是这样,探针按照二次方跳转,根据需要环绕哈希表。
由于这是一种不同的二次探测,所以一个非常常见的错误就是:做H(V),(H(v)的1)%M,(H(v)的+ 1 + 4)%M,(H(V)+ 1 + 4 + 9)%M,...
X Esc
上一个 PgUp
下一个 PgDn
假设我们已经调用 Insert(18)和Insert(10)到大小为M = HT.length = 7的初始空Hash表中。由于18%7 = 4和10%7 = 3,18和3不会相互碰撞, 如上所示分别位于指数4和3中。
现在,我们点击Insert(38)
回顾(点击上面的按钮后显示)。
X Esc
上一个 PgUp
下一个 PgDn
删除(x)和搜索(y)操作的定义类似。 只是这次我们使用二次探测而不是线性探测。
例如,假设我们在上一张幻灯片之后调用了Remove(18),并且标记了HT [4] = DEL。 如果我们调用Search(38),我们将使用与上一张幻灯片相同的二次探测序列,但是会通过标记为DELETED的HT [4]。
X Esc
上一个 PgUp
下一个 PgDn
一目了然,二次探测(Quadratic Probing)能够快速解决我们之前对线性探测的主要聚类问题,但它是完美的碰撞解决技术吗?
尝试Insert([12,17])
你意识到刚刚发生了什么吗?
X Esc
上一个 PgUp
下一个 PgDn
我们可以很容易地插入12,因为h(12)= 12%7 = 5 以前是空的(见上文)。
但是,即使我们仍然有3个空槽,我们仍然存在主要问题:h(17)= 17%7 = 3已被键10占用,(3 + 1 * 1)%7 = 4已被占用 (3 + 2 * 2)%7 = 0已被键38占用,(3 + 3 * 3)%7 = 5已被键12占用,(3 + 4 * 4)%7 = 5 (3 + 5 * 5)%7 = 0再次被键38占据,(3 + 6 * 6)%7 = 4又被键18占据,(3 + 7 * 7)%7 = 3再次被键10占用,如果我们继续二次探测,它将永远循环...
尽管我们仍然有几个(3)空单元格,但我们无法将此新值17插入到哈希表中。
X Esc
上一个 PgUp
下一个 PgDn
如果α<0.5且M是质数(> 3),那么我们总是可以使用二次探测找到一个空槽。 回想一下:α是加载因子,M是散列表大小(HT.length)。

如果满足上述两个要求,我们可以证明包括基地址h(v)的第一M / 2二次探测指数都是不同且唯一的。

但除此之外没有这样的保证。 因此,如果我们想要使用二次探测,我们需要确保α<0.5(在这个可视化中没有强制执行,但是为了防止无限循环,我们在M步之后打破了循环)。
X Esc
上一个 PgUp
下一个 PgDn
我们将通过矛盾使用证据。 我们首先假设两个二次探测步骤:x和y,x!= y(假设x
h(v) + x*x = h(v) + y*y (mod M)
x*x = y*y (mod M)//从两边敲出h(v)
x*x - y*y = 0 (mod M) //将y * y移动到左边
(x-y)*(x+y) = 0 (mod M)//重新排列公式

现在,(x-y)或(x + y)必须等于零。 我们的假设是x != y,  那么(x-y)不能为0. 由于0≤ x
矛盾!
因此,第一个M / 2二次探测步骤不能产生相同的地址模M(如果我们将M设置为大于3的质数)。
讨论:我们可以使二次探测能够使用其他约50%的表格单元吗?
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn
在二次探测中,群集(clusters)沿着探测路径形成,而不是像线性探测那样围绕基地址形成。 这些群集称为次级群集(Secondary Clusters)。
由于在所有密钥的探测中使用相同的模式,所以形成次级群集。 请注意,如果两个不同的键具有相同的基址,则它们的二次探测序列将是相同的。
二次探测中的次级群集不如线性探测中的主群集那样糟糕,因为理论上散列函数理论上应该首先将键分散到不同的基地址∈[0..M-1]中。
X Esc
上一个 PgUp
下一个 PgDn
为了减少主要和次要群集,我们可以修改探针序列为:

  h(v)//基地址
(h(v)+ 1 * h2(v))%M //第一个探测步骤,如果有碰撞
(h(v)+ 2 * h2(v))%M //第2次探测步骤,如果仍有冲突
(h(v)+ 3 * h2(v))%M //第三次探测步骤,如果仍有冲突
...
(h(v)+ k * h2(v))%M //第k个探测步骤等...
就是这样,探测器根据第二个散列函数h2(v)的值跳转,根据需要环绕散列表。
X Esc
上一个 PgUp
下一个 PgDn
如果h2(v)= 1,则双散列(Double Hashing)的工作方式与线性探测(Linear Probing)完全相同。 所以我们通常希望h2(v)> 1来避免主聚类。
如果h2(v)= 0,那么Double Hashing不起作用的原因很明显,因为任何探测步数乘以0仍然是0,即我们在碰撞期间永远停留在基地址我们需要避免这种情况。
通常(对于整数键),h2(v)= M' - v%M'其中M'是一个小于M的质数。这使得h2(v)∈[1..M'],它足够多样 二次聚类。
二次散列函数的使用使得理论上难以产生主要或次要群集问题。
X Esc
上一个 PgUp
下一个 PgDn
点击Insert([35,42])插入35,然后插入42到上面的当前哈希表。
回顾(点击上面的按钮后显示)。
X Esc
上一个 PgUp
下一个 PgDn
删除(x)和搜索(y)操作的定义类似。 只是这次我们使用双散列(Double Hashing)而不是线性探测或二次探测。
例如,假设我们在上一张幻灯片之后调用了Remove(17),并且标记了HT [3] = DEL。 如果我们调用Search(35),我们将使用与前一张幻灯片相同的双哈希序列,但是会通过标记为DELETED的HT [3]。
X Esc
上一个 PgUp
下一个 PgDn
总之,一个好的开放寻址冲突解决技术需要:
  1. 如果它存在,总是找到一个空插槽,
  2. 最小化聚类(任何类型),
  3. 当两个不同的键碰撞时, 给出不同的探针序列,
  4. 快,O(1)。
讨论:双散列(Double Hashing)似乎符合。 但是...... 双散列策略是否足够灵活,可用作哈希表的默认库的实现? 让我们来看看...
X Esc
上一个 PgUp
下一个 PgDn
如果我们使用分离链接法作为冲突解决技术,请尝试Insert([9,16,23,30,37,44])以查看Insert(v)操作是如何工作的。 请注意,所有整数{9,16,23,30,37,44}都是2(模7),因此所有整数都将被添加到双链表2的后面,并且每个插入显然都是O(1)。
由于屏幕限制,我们将每个双链表的长度限制为6。
X Esc
上一个 PgUp
下一个 PgDn
尝试Search(35)以查看可以使Search(v )在O(1 +α)中运行。
尝试Remove(7)以查看Remove(v) 也可以在O(1 +α)中运行。
如果α很大,分离连接法性能不是真的O(1)。 然而,如果我们粗略地知道我们的应用程序将使用的键的可能数量n,那么我们可以相应地设置表格大小m,使得α= n / m是非常低的正数,从而使分离链接性能全部为O(1)。
X Esc
上一个 PgUp
下一个 PgDn
讨论:在所有这些解释之后,两种冲突解决方法的哪一种更好?
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn

您已经完成了这个哈希表数据结构的基本工作,我们鼓励您在探索模式中进一步探索。

但是,我们仍然会为您提供一些本节中概述的更有趣的哈希表的挑战。

X Esc
上一个 PgUp
下一个 PgDn
当负载因子α变高时,哈希表的性能会降低。 对于(标准)二次探测冲突解决方法,当哈希表的α> 0.5时,插入可能失败。
如果发生这种情况,我们可以重新散列(rehash)。 我们用一个新的散列函数构建另一个大约两倍的散列表。 我们遍历原始哈希表中的所有键,重新计算新的哈希值,然后将键(及其卫星数据)重新插入新的更大的哈希表中,最后删除较早的较小哈希表。
一个经验法则是,如果使用开放寻址,并且当α>小的常数(根据需要接近1.0),如果使用单独链接,当α≥0.5时重新散列。
如果我们知道可能的最大键数,我们可以始终将α作为一个低的数字。
X Esc
上一个 PgUp
下一个 PgDn

但是,如果您需要使用C ++或Java实现哈希表,并且您的键(Keys)是整数或字符串,则可以使用内置的C ++ STL或Java API。 他们已经有了整数或字符串默认散列函数的内置实现。
请参阅C ++ STL unordered_mapunordered_set 或 Java HashMapHashSet
请注意,multimap / multiset实现也存在(允许重复键)。
但是,这里是我们在C ++中使用一个简单的分离链接法实现(虽然不够通用)。


X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn

如果(整数或字符串)键只需要映射到卫星数据,则散列表是实现Table ADT的非常好的数据结构,对于Search(v),Insert(v)和Remove( v)如果哈希表设置正确,则执行时间复杂度为O(1)。
但是,如果我们需要更多地使用 键,我们可能需要使用另一种数据结构。

X Esc
上一个 PgUp
下一个 PgDn

关于这个数据结构的一些更有趣的问题,请在哈希表培训模块上练习(不需要登录,但是只需短暂且中等难度设置)。
但是,对于注册用户,您应该登录,然后转到主要培训页面以正式清除此模块,这些成果将记录在您的用户帐户中。

X Esc
上一个 PgUp
下一个 PgDn

尝试解决一些基本的编程问题,就很可能需要使用哈希表(特别是如果输入大小很大的时候):

  1. Kattis - cd(输入已经被排序,因此存在替代的非哈希表解决方案; 如果未对输入进行排序,则在哈希表的帮助下最好解决此组交集问题),
  2. Kattis - oddmanout(我们可以将较大的邀请代码映射到较小范围的整数;这是整数散列(大范围)的一种做法),
  3. Kattis - whatdoesthefoxsay(我们把不是狐狸的声音放入无序集合;这是散列字符串的做法)。

X Esc
上一个 PgUp
下一个 PgDn
当操作进行时,状态面板将会有每个步骤的描述。
X Esc
上一个 PgUp
下一个 PgDn

e-Lecture: The content of this slide is hidden and only available for legitimate CS lecturer worldwide. Drop an email to visualgo.info at gmail dot com if you want to activate this CS lecturer-only feature and you are really a CS lecturer (show your University staff profile).

X Esc
上一个 PgUp
下一个 PgDn

Control the animation with the player controls! Keyboard shortcuts are:

Spacebar: play/pause/replay
Left/right arrows: step backward/step forward
-/+: decrease/increase speed
X Esc
上一个 PgUp
下一个 PgDn

Return to 'Exploration Mode' to start exploring!


Note that if you notice any bug in this visualization or if you want to request for a new visualization feature, do not hesitate to drop an email to the project leader: Dr Steven Halim via his email address: stevenhalim at gmail dot com.

X Esc
上一个 PgUp

创建

插入(v)

移除(v)

>

创建空的哈希表大小为 =

执行

v =

执行

v =

执行

关于 团队 使用条款

关于

VisuAlgo在2011年由Steven Halim博士概念化,作为一个工具,帮助他的学生更好地理解数据结构和算法,让他们自己和自己的步伐学习基础。
VisuAlgo包含许多高级算法,这些算法在Steven Halim博士的书(“竞争规划”,与他的兄弟Felix Halim博士合作)和其他书中讨论。今天,一些高级算法的可视化/动画只能在VisuAlgo中找到。
虽然专门为新加坡国立大学(NUS)学生采取各种数据结构和算法类(例如CS1010,CS1020,CS2010,CS2020,CS3230和CS3230),作为在线学习的倡导者,我们希望世界各地的好奇心发现这些可视化也很有用。
VisuAlgo不是从一开始就设计为在小触摸屏(例如智能手机)上工作良好,因为需要满足许多复杂的算法可视化,需要大量的像素和点击并拖动手势进行交互。一个令人尊敬的用户体验的最低屏幕分辨率为1024x768,并且只有着陆页相对适合移动设备。
VisuAlgo是一个正在进行的项目,更复杂的可视化仍在开发中。
最令人兴奋的发展是自动问题生成器和验证器(在线测验系统),允许学生测试他们的基本数据结构和算法的知识。这些问题是通过一些规则随机生成的,学生的答案会在提交给我们的评分服务器后立即自动分级。这个在线测验系统,当它被更多的世界各地的CS教师采用,应该技术上消除许多大学的典型计算机科学考试手动基本数据结构和算法问题。通过在通过在线测验时设置小(但非零)的重量,CS教练可以(显着地)增加他/她的学生掌握这些基本问题,因为学生具有几乎无限数量的可以立即被验证的训练问题他们参加在线测验。培训模式目前包含12个可视化模块的问题。我们将很快添加剩余的8个可视化模块,以便VisuAlgo中的每个可视化模块都有在线测验组件。
另一个积极的发展分支是VisuAlgo的国际化子项目。我们要为VisuAlgo系统中出现的所有英语文本准备一个CS术语的数据库。这是一个很大的任务,需要众包。一旦系统准备就绪,我们将邀请VisuAlgo游客贡献,特别是如果你不是英语母语者。目前,我们还以各种语言写了有关VisuAlgo的公共注释:
zh, id, kr, vn, th.

团队

项目领导和顾问(2011年7月至今)
Dr Steven Halim, Senior Lecturer, School of Computing (SoC), National University of Singapore (NUS)
Dr Felix Halim, Software Engineer, Google (Mountain View)

本科生研究人员 1 (Jul 2011-Apr 2012)
Koh Zi Chun, Victor Loh Bo Huai

最后一年项目/ UROP学生 1 (Jul 2012-Dec 2013)
Phan Thi Quynh Trang, Peter Phandi, Albert Millardo Tjindradinata, Nguyen Hoang Duy

最后一年项目/ UROP学生 2 (Jun 2013-Apr 2014)
Rose Marie Tan Zhao Yun, Ivan Reinaldo

本科生研究人员 2 (May 2014-Jul 2014)
Jonathan Irvin Gunawan, Nathan Azaria, Ian Leow Tze Wei, Nguyen Viet Dung, Nguyen Khac Tung, Steven Kester Yuwono, Cao Shengze, Mohan Jishnu

最后一年项目/ UROP学生 3 (Jun 2014-Apr 2015)
Erin Teo Yi Ling, Wang Zi

最后一年项目/ UROP学生 4 (Jun 2016-Dec 2017)
Truong Ngoc Khanh, John Kevin Tjahjadi, Gabriella Michelle, Muhammad Rais Fathin Mudzakir

List of translators who have contributed ≥100 translations can be found at statistics page.

致谢
这个项目是由来自NUS教学与学习发展中心(CDTL)的慷慨的教学增进赠款提供的。

使用条款

VisuAlgo是地球上的计算机科学社区免费。如果你喜欢VisuAlgo,我们对你的唯一的要求就是通过你知道的方式,比如:Facebook、Twitter、课程网页、博客评论、电子邮件等告诉其他计算机方面的学生/教师:VisuAlgo网站的神奇存在
如果您是数据结构和算法学生/教师,您可以直接将此网站用于您的课程。如果你从这个网站拍摄截图(视频),你可以使用屏幕截图(视频)在其他地方,只要你引用本网站的网址(http://visualgo.net)或出现在下面的出版物列表中作为参考。但是,您不能下载VisuAlgo(客户端)文件并将其托管在您自己的网站上,因为它是剽窃。到目前为止,我们不允许其他人分叉这个项目并创建VisuAlgo的变体。使用(客户端)的VisuAlgo的离线副本作为您的个人使用是很允许的。
请注意,VisuAlgo的在线测验组件本质上具有沉重的服务器端组件,并且没有简单的方法来在本地保存服务器端脚本和数据库。目前,公众只能使用“培训模式”来访问这些在线测验系统。目前,“测试模式”是一个更受控制的环境,用于使用这些随机生成的问题和自动验证在NUS的实际检查。其他感兴趣的CS教练应该联系史蒂文如果你想尝试这样的“测试模式”。
出版物名单
这项工作在2012年ACM ICPC世界总决赛(波兰,华沙)和IOI 2012年IOI大会(意大利Sirmione-Montichiari)的CLI讲习班上进行了简要介绍。您可以点击此链接阅读我们2012年关于这个系统的文章(它在2012年还没有被称为VisuAlgo)。
这项工作主要由我过去的学生完成。最近的最后报告是:Erin,Wang Zi,Rose,Ivan。
错误申报或请求添加新功能
VisuAlgo不是一个完成的项目。 Steven Halim博士仍在积极改进VisuAlgo。如果您在使用VisuAlgo并在我们的可视化页面/在线测验工具中发现错误,或者如果您想要求添加新功能,请联系Dr Steven Halim博士。他的联系邮箱是他的名字加谷歌邮箱后缀:StevenHalim@gmail.com。