哈希表/字典冲突

Posted

技术标签:

【中文标题】哈希表/字典冲突【英文标题】:Hashtable/Dictionary collisions 【发布时间】:2010-10-18 15:08:45 【问题描述】:

仅使用标准英文字母和下划线,最多可以使用多少个字符而不会导致哈希表/字典中的潜在冲突。

所以像这样的字符串:

blur
Blur
b
Blur_The_Shades_Slightly_With_A_Tint_Of_Blue

...

【问题讨论】:

【参考方案1】:

不能保证单个字母之间不会发生冲突。

可能不会,但string.GetHashCode 中使用的算法未指定,并且可能会更改。 (特别是它在 .NET 1.1 和 .NET 2.0 之间发生了变化,这让那些认为它不会改变的人感到很痛苦。)

请注意,哈希码冲突不会阻止精心设计的哈希表工作 - 您应该仍然能够获取正确的值,它可能需要使用相等性检查多个键,如果它们有相同的哈希码。

任何依赖哈希码唯一的字典都缺少关于哈希码的重要信息,IMO :)(除非它在非常绝对知道它们的特定条件下运行'将是独一无二的,即它使用perfect hash function。)

【讨论】:

仅供参考,已知的唯一性称为完美散列,除了 MD5 和 SHA1 和之外,现在很少见到。 它只有在哈希码空间大于潜在密钥空间时才可能工作,对于初学者来说:) (将编辑答案以参考关于完美哈希的***文章。) 谢谢乔恩。我明白你的意思了。我知道我不需要完美的唯一性,只是想如果我的字符数量有限,也许这会保证哈希表中的唯一性,以避免额外的查找。我也可以在反射器中看到 string.GetHashCode 吗? 顺便说一下 MD5 或 SHA 不是完美的哈希值。它们只是非常复杂且计算量大的优秀哈希。对于非加密用途,您也可以使用便宜得多的产品。【参考方案2】:

如果您的密钥是一个字符串(例如,一个字典),那么它的 GetHashCode() 将被使用。那是一个 32 位整数。 Hashtable 默认使用 1 键来评估负载因子,并增加存储桶的数量以保持该负载因子。因此,如果您确实看到冲突,它们应该倾向于发生在重新分配边界附近(并在重新分配后不久减少)。

【讨论】:

【参考方案3】:

哈希算法不应该保证唯一性。鉴于潜在的字符串(n 长度为 26^n,甚至忽略特殊字符、空格、大写、非英语字符等)比哈希表中的位置多得多,因此无法实现这样的保证.它只是为了保证良好的分布。

【讨论】:

【参考方案4】:

给定一个perfect hashing function(您通常不会拥有,正如其他人所提到的),您可以找到最大可能个字符,以保证没有两个字符串会产生冲突,如下:


没有。 avilable 的唯一哈希码数 = 2 ^ 32 = 4294967296(假设哈希码使用 32 位整数) 字符集大小 = 2 * 26 + 1 = 53(26 个小写字母在拉丁字母中作为大写字母,加上下划线)

那么你必须考虑长度为l(或更短)的字符串总共有54 ^ l 表示。请注意,基数是 54 而不是 53,因为字符串可以在任何字符之后终止,从而为每个字符添加额外的可能性 - 并不是说​​它对结果有很大影响。

选择没有。唯一哈希码作为字符串表示的最大数量,您会得到以下简单等式:

54 ^ l = 2 ^ 32

并解决它:

log2 (54 ^ l) = 32
l * log2 54 = 32
l = 32 / log2 54 = 5.56

(其中 log2 是以 2 为底的对数函数。)

由于字符串长度显然不能是小数,因此您采用整数部分来给出最大长度仅为 5。确实很短,但请注意,在给定完美哈希函数的情况下,此限制甚至可以防止最微小的冲突机会。


然而,正如我所提到的,这在很大程度上是理论上的,我不确定它在任何设计考虑中可能有多少用途。话虽如此,希望它可以帮助您从理论角度理解问题,在此之上您可以添加实际考虑因素(例如不完美的哈希函数,分布不均匀)。

【讨论】:

+1 我非常喜欢你的数学,所以我自己做了。介意我将你的数学整合到我的答案中,以包括完美哈希的实际限制吗? @ShuggyCoUk:是的,没问题...我很想看看你想出了什么。 :) 在 cmets 中集成和归因,现在如果您愿意,可以随意编辑社区 wiki。谢谢【参考方案5】:

通用哈希

计算与S 长度为L 的字符串与每个字符的W 位与长度为H 位的哈希的冲突概率,假设最佳universal hash (1 ) 您可以根据大小(桶数)“N”的哈希表计算碰撞概率。

首先,我们可以假设一个理想的哈希表实现 (2),它将哈希中的 H 位完美地拆分到可用的桶中 N(3 )。这意味着H 变得毫无意义,除非作为N 的限制。 W 和“L”只是S 上限的基础。对于更简单的数学假设字符串长度 L 只是用特殊的空字符填充到 L 。如果我们感兴趣,我们对最坏的情况感兴趣,这是 54^L (26*2+'_'+ null),显然这是一个可笑的数字,实际的条目数比字符集更有用,并且长度,所以我们将像 S 本身就是一个变量一样工作。

我们试图将S 项目放入N 存储桶中。 这将成为一个众所周知的问题,birthday paradox

为各种概率和桶数解决这个问题是instructive,但假设我们有 10 亿个桶(在 32 位系统中大约有 4GB 内存),那么我们只需要 37K 条目就可以达到 50% 的机会他们至少是一次碰撞。鉴于试图避免散列表中的任何冲突显然是荒谬的。

这并不意味着我们不应该关心我们的哈希函数的行为。显然,这些数字是假设理想的实现,它们是我们能获得多好的上限。一个糟糕的散列函数会在某些区域产生更严重的冲突,通过从不或很少使用它来浪费一些可能的“空间”,所有这些都可能导致散列不是最优的,甚至会降低到看起来像列表的性能,但有很多更糟糕的常数因素。

.NET 框架对字符串散列函数的实现不是很好(因为它可能会更好),但对于绝大多数用户来说可能是可以接受的,并且计算效率相当高。

另一种方法:完美散列

如果您希望可以生成所谓的perfect hashes,这需要事先完全了解输入值,但这通常不是很有用。与上述数学类似,我们可以证明即使是完美的哈希也有其局限性:

回忆长度为 L 的 54 ^ L 字符串的限制。然而,我们只有H 位(我们假设为 32),大约有 40 亿个不同的数字。因此,如果您可以拥有真正的 any 字符串和任意数量的字符串,那么您必须满足:

54 ^ L <= 2 ^ 32

并解决它:

log2 (54 ^ L) <= 32
L * log2 54 <= 32
L <= 32 / log2 54 <= 5.56

由于字符串长度显然不能是小数,因此您的最大长度仅为 5。确实很短。

如果你知道你只会有一组大小远低于 40 亿的字符串,那么完美的散列可以让你处理 L 的任何值,但在实践中限制一组值可能非常困难,你必须提前知道它们,否则会降级为字符串数据库 -> 哈希并在遇到新字符串时添加到其中。


    对于这个练习,通用散列是最优的,因为我们希望减少 任何 冲突的概率,即对于任何输入,它具有来自一组可能性 R 的输出 x 的概率是 1/R。

    请注意,在散列(和内部分桶)上进行优化工作非常困难,但您应该期望内置类型即使并不总是理想也是合理的。

    在这个例子中,我避免了封闭和开放寻址的问题。这确实对所涉及的概率有一些影响,但并不显着

【讨论】:

这个答案现在反映了 Noldorin 在完美散列方面的努力,现在社区 wiki 也是如此

以上是关于哈希表/字典冲突的主要内容,如果未能解决你的问题,请参考以下文章

redis基础结构-DICT

redis中的hash

Redis底层解析字典类型

查找:哈希表

哈希表原理及如何避免键值冲突法?

redis字典结构终结篇你确定不看吗?