散列法:双重散列
密钥是您的帐号,值是您的完整信息。双重哈希是一种计算机编程技术,它与哈希表中的开放寻址结合使用,以解决哈希冲突,方法是在发生冲突时使用密钥的辅助哈希作为偏移量。在大多数实际场景中,键的数量远远大于哈希表中的位置数量,因此,无论哈希函数有多好,都必然会发生冲突。,现在这又是一次冲突,因为第一个位置已经被占用,我们现在需要使用双重哈希来解决这个冲突。2(k).....,这导致了对哈希表进行寻址的非线
概述
双重哈希是一种计算机编程技术,它与哈希表中的开放寻址结合使用,以解决哈希冲突,方法是在发生冲突时使用密钥的辅助哈希作为偏移量。
要点
- 双哈希算法的复杂性
- 时间复杂度 - O(n)
双重散列简介
您是否曾经与银行客户服务主管交谈过?对于任何投诉或疑问,他们首先会问您您的银行帐号。你觉得怎么样?他们为什么需要您的帐号?
是的,没错!您的帐号显示有关您的所有信息。对于每个客户,银行都维护一个键值对。密钥是您的帐号,值是您的完整信息。我们可以说银行已经以帐号的形式对您的全部信息进行了哈希处理。
哈希是一种强大的技术,用于在平均恒定时间内存储和检索数据。在这种技术中,我们将数据或一些键存储在称为哈希表的固定大小的数组结构中。
每个键都映射到哈希表上的特定位置。此映射称为哈希函数。理想的哈希函数必须将每个唯一键映射到哈希表上的唯一位置。
在大多数实际场景中,键的数量远远大于哈希表中的位置数量,因此,无论哈希函数有多好,都必然会发生冲突。
当两个或多个不同的键被哈希处理到哈希表的同一位置时,就会发生冲突。为了解决这些冲突,我们需要某种冲突解决技术。其中一种有效的冲突解决技术是双重散列。
什么是双重哈希?
在双重哈希中,我们使用两个哈希函数。第一个哈希函数是,这个函数接受我们的键,并在哈希表上给出一个位置。如果新位置是空的,我们可以轻松地将密钥放入其中,而无需使用辅助哈希函数。
但是,在发生冲突的情况下,我们需要使用辅助哈希函数 与第一个哈希函数结合使用 在哈希表上找到一个新位置。 使用的组合哈希函数的格式为。在这里,i 是一个非负整数,表示冲突数,k = 正在散列的元素/键,m = 散列表大小。
辅助哈希函数的使用 碰撞后,帮助我们到达哈希表上的新位置,每个新位置的距离为......,这导致了对哈希表进行寻址的非线性方式,从而减少了冲突的次数。
需要考虑的非常重要的一点是,两个哈希函数都应按 O(1) 时间的顺序计算。
-
在图中,我们有一个大小为 5 的哈希表和两个键 10、15,需要插入到哈希表中。我们在这里使用两个哈希函数,第一个哈希函数 = = key % 5和辅助哈希函数 == key % 7
-
我们从第一个键 10 开始,并应用我们的第一个哈希函数来获取 10 在哈希表上的位置。
位置。现在第 0 个位置是空的,因此我们可以将 10 个位置放在那里。这里不需要使用辅助哈希函数。 -
现在,我们取第二个键 15 并再次应用我们的第一个哈希函数。
位置。哎呀!这一次位置不是空的,因此是碰撞。我们需要为键 15 找到一个新位置,为此我们使用辅助哈希函数。
新位置。哈希表的第一个位置是空的,我们可以在这里放置键 15。
数据结构中的双重哈希示例
双重哈希背后的想法相当简单,
- 获取要存储在哈希表上的密钥。
- 应用第一个哈希函数ℎ1h1(key) 以获取存储密钥的位置。
- 如果该位置为空,请将密钥放在该位置上。
- 如果位置已填充,请应用第二个哈希函数ℎ2h2(键)结合第一个哈希函数ℎ1h1(键)以获取密钥的新位置。
让我们通过一个动手问题更详细地探讨这个想法,
问题陈述:
将键 79、69、98、72、14、50 插入大小为 13 的哈希表中。使用双哈希解决所有冲突,其中第一个哈希函数是 = k mod 13第二个哈希函数是
溶液:
最初,所有哈希表位置都是空的。我们选择第一个键 = 79 并应用在它上面,
ℎ1h1(79) = 79 % 13 = 1,因此键 79 哈希值为哈希表的位置。但是在放置密钥之前,我们需要确保哈希表的位置为空。在我们的例子中,它是空的,我们可以轻松地将钥匙 79 放在那里。
第二个键 = 69,我们再次申请 在它上面,,因为位置是空的,我们可以把69放在那里。
第三个键 = 98,我们应用 在它上面, = 98 % 13 = 7,位置是空的,所以98放在那里。
第四个键 = 72, = 72 % 13 = 7,现在这是一个碰撞,因为位置已被占用,我们需要使用双重哈希来解决此冲突。
= [ 7 + 1 * ( 1 + 72 % 11) ] % 13
= 1
位置已经在哈希表中被占用,因此我们再次发生冲突。现在我们再次重新计算 i = 2
= [ 7 + 2 * ( 1 + 72 % 11) ] % 13
= 8
位置在哈希表中是空的,现在我们可以将键 72 放在那里。
第五个键 = 14,(14) = 14%13 = 1,现在这又是一次冲突,因为第一个位置已经被占用,我们现在需要使用双重哈希来解决这个冲突。
= [ 1 + 1 * ( 1 + 14 % 11) ] % 13
= 5
位置是空的,我们可以轻松地将 14 号钥匙放在那里。
第六个键 = 50,(50) = 50%13 = 11,位置已经空了,我们可以把钥匙50放在那里。
由于所有键都放在我们的哈希表中,因此完成了双重哈希过程。最后,我们的哈希表如下所示:
为什么要使用双重哈希?
双重哈希是流行的冲突解决技术之一。其他具有相同目的的流行变体是线性探测和二次探测。 但是,如果有其他技术可用,那么为什么我们首先需要双重散列呢?
双重哈希提供了更好的抗聚类能力。造成这种情况的一个主要原因是使用双重功能。双函数支持双重哈希,以执行更均匀的密钥分布,从而减少冲突。
数据结构中双重哈希的优点
- 每次碰撞后,我们都会重新计算哈希表中元素的新位置。对于连续的碰撞,这将生成一系列位置。如果序列是唯一的,则碰撞次数较少。
- 双重哈希创建最独特的序列,在哈希表中提供更均匀的键分布。
- 如果哈希函数不够好,元素往往会在哈希表中形成分组。此问题称为聚类分析。双重哈希最不容易出现聚类。
基于双重哈希的练习题
问题陈述 1:
给定两个哈希函数,(k) = k mod 23和(k) = 1 + k mod 19.假设表大小为 23。查找键 = 90 的第二次碰撞后通过双重哈希返回的地址。
溶液:
我们将使用双重哈希的公式-
h(k,i) = ((k) + i *(k) )%m
正如它所给出的,k = 90,m = 23
由于第二次碰撞已经发生,i = 2。 代入上述公式中的值,我们得到,
h(90,2) = [ ( 90 % 23 ) + 2 * ( 1 + 90 % 19 ) ] % 23
= [ 21 + 2 * 15 ] % 23
= 5
因此,在第二次碰撞后,Key = 90 的双重哈希返回的地址为 5。
问题陈述 2:
给定一个正整数的 Array[] 和一个数字 n,找出数组中是否存在一对总和等于 n 的数字。如果存在这样的对,则返回 true 否则为 false。
例:
-
输入:- A = [ 6, 4, 3, 1 ] 和 n = 7 输出:- 没错,对是 {4,3} 或 {6,1}
解释:- 4 + 3 = 7 或 6 + 1 = 7 -
输入:- A = [ 6, 4, 3, 1 ] 和 n = 8 输出:- 假,不存在
这样的对 解释:- 数组中没有一对数字,其相加总和为 8
在转向解决方案之前,只需停下来思考一下
溶液:
以下 JAVA 代码提供了解决方案。
boolean isPairSumPresent(int[] arr, int n) {
HashSet<Integer> hSet = new HashSet<>();
for (int num : arr) {
int diff = n - num;
if (hSet.contains(diff)) {
return true;
} else {
hSet.add(num);
}
}
return false;
}
- 我们维护一个整数的哈希集,用于存储数组数的补码。
- 哈希集确保每次查找都可以在平均 O(1) 时间内完成。它在内部维护所谓的存储桶,然后尝试根据哈希函数在这些存储桶之间分配我们的元素。
- 核心逻辑是迭代检查我们的哈希集是否包含所需的补码数。例如:如果总和是 8,数组的第一个元素是 3,那么在哈希集中,我们检查 5 (8 - 3) 是否存在。
- 如果哈希集中不存在这样的补码,那么我们只需将数组号放入哈希集中并继续前进。
- 如果存在这样的补码,那么我们返回 true 并停止。
双重散列的时间复杂度:
- 哈希集中的每次查找都会花费 O(1) 个恒定时间。
- 在最坏的情况下,我们可能无法找到这样的赞美并遍历整个数组,这将花费我们 O(n) 线性时间。
- 像减法这样的静止运算只是恒定时间运算。
- 因此,总时间复杂度 = O(n) 时间。
双重散列的空间复杂度:
- 我们需要维护一个额外的哈希集,其大小不超过 n 个元素,这会消耗我们额外的 O(n) 空间。
- 因此,总空间复杂度 = O(n) 时间。
结论
- 在本文中,我们揭开了双重哈希的神秘面纱。我们亲身体验了它的工作和应用。
- 最后,我们利用了它的优势,在恒定的平均时间内进行了搜索。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)