【问题标题】:How likely is it to get a HashCode collision with this hashcode function?与此哈希码函数发生 HashCode 冲突的可能性有多大?
【发布时间】:2011-03-17 12:36:56
【问题描述】:

在以下情况下,与下面的函数发生 HashCode 冲突的可能性有多大。

  1. key[0],key[1],key[2],key[3] 的随机 int 值
  2. 具有以下约束的随机键值
    • 键[0]
    • 键[1]
    • 键[2]
    • 键[3]

假设我们有 1000 万个对象。

int[] key=new int[4];    
public override int GetHashCode()
{
    // Use large prime multiples to create a unique hash key
    // Create the hash offsets using a "even powers of 2 minus 1" method, which gives 
    // primes most of the time.  
    int hashKey = 0;
    hashKey += 2047 * key[0];
    hashKey += 8191 * key[1];
    hashKey += 32767 * key[2];
    hashKey += 131071 * key[3];
    return hashKey;
}

【问题讨论】:

  • 不。现实生活。那是我们的哈希函数,我们现在正试图解决更大的问题。试图评估这将是一个多大的问题。
  • 在什么情况下.NET内置的GetHashCode()不足?我从未发现无法正确区分对象的问题。
  • @Tejs 内置的GetHashCode 是基于引用相等(当然仅适用于引用类型)。如果你想要价值平等,你需要覆盖它(连同.Equals)。所以我真的不明白你在说什么。
  • 我只是好奇,因为我从未见过需要它的示例。这也是我不回答的原因,因为我很好奇答案。
  • @derdo 您可以考虑使用Tuple<int,int,int,int>。不确定它是否符合您的需要。

标签: c# data-structures hashcode


【解决方案1】:

这是一个奇怪的问题。让我们从代码中明显的错误开始:

// Use large prime multiples to create a unique hash key     
// Create the hash offsets using a "even powers of 2 minus 1" method, which gives      
// primes most of the time.   

首先,这些都是二减一的奇次幂;它们都不是二减一的幂。

其次,在您选择作为“大素数倍数”的四个乘数中,有一半不是素数。 2047 和 32767 是复合的。

第三,如果我们“正确”——而且我使用这个词是明智的——陈述是“2 的奇数次幂减一,它在大多数情况下给出素数”,该陈述是荒谬的错误的。这种形式的素数称为梅森素数,已知的梅森素数只有 47 个。我向你保证,梅森素数的密度远低于二分之一。这么说吧:在 2^1-1 和 2^43112609−1 之间的奇次幂梅森数中,已知有 46 个是素数,大约是百万分之一,而不是一半。

第四,你认为质数与什么有什么关系?素数有什么神话般的力量?当然,重要的是散列码的分布往往不会产生散列表大小的倍数。由于哈希表的大小被选择为素数,这似乎是潜在的加剧问题。

第五,哈希键不唯一;您的问题是关于它们何时发生碰撞,因此显然它们不可能是唯一的。

第六,假设您的哈希函数在 32 位整数空间中具有完全随机分布。通过生日“悖论”,您预计从 32 位空间中随机抽取一千万个数字时,发生至少一次碰撞的可能性远远大于 99%。事实上,预计的碰撞次数将在十或两万次左右。 (我们可以计算出预期碰撞的确切数量,但谁在乎它究竟是什么;它在那个数量级。)

是不是冲突太多了?比随机分布做得更好是非常困难的。如果您需要更少的冲突,那么您一开始就不应该使用 32 位散列算法。

第七,谁在乎哈希函数在其整个范围内有多少次冲突?当然,实际问题应该是“这个散列如何处理大表中的实际数据?”与我们不同,您可以通过尝试来回答这个问题。如果它符合您的性能预算,很好,请担心其他事情。如果没有,请在开始指责散列函数之前弄清楚为什么不这样做。

我对这个问题以及您希望从它的答案中获得什么感到非常困惑。你能解释一下吗?

【讨论】:

  • 您好,感谢您的详细回答。我想我的问题已经证明 cmets 撒谎:) 也感谢您的详细更正。没有过多的细节,我得到的碰撞越多,我的优化算法的性能就越差,它优化了一些伪利润函数。由于我们的优化工作方式,该哈希函数中的冲突数量较少意味着结果更接近理论最优值。如果它们是独一无二的,我保证得到最好的结果。我试图评估我现在是否需要不同的解决方案(大量的碰撞)还是可以等待。
  • 所以我非常实际,并试图将问题保持在我感兴趣的确切内容上,而不会造成混乱或花费太多时间。再次对代码中的混淆和错误的 cmets 表示歉意。
  • @derdo:除了平衡哈希表之外,您是否将哈希函数用于其他的事情? 不要那样做。 GetHashCode 旨在在将数千个事物填充到内存哈希表中时产生合理的桶大小平衡;它不是为了在大型数据集上产生最优的、无冲突的散列而设计的。我注意到,根据您的问题陈述,只有 10^16 = 2^54 个可能的值。你能生成一个唯一的 64 位散列吗?这样做很简单。
  • 是的,我们正在使用 GetHashCode 创建优化组。同一个优化组中的产品一起优化,结果可能比单独优化要差一些。当问题规模很小且碰撞概率很小时,它非常方便。现在看起来我需要别的东西,可能会尝试 64 位哈希。
  • @derdo:我认为 64 位散列是要走的路。显然,您可以转换为 long 并将每个字段乘以 1000000、10000000000 等,将结果相加,您将拥有一个唯一的 64 位数字。仅供参考,计算从一组大小 d 中选择 n 个数字时预期重复次数的方法是 (n - d + (d) x (d-1/d)^n)。如果您选择 n 为 10^16,d 为 2^32,您将看到仅基于运气不好的预期碰撞次数约为 10-20000。
【解决方案2】:

我写了一个快速脚本来测试这个。

import random

def hash(key):
    hashKey = 0
    hashKey += 2047 * key[0]
    hashKey += 8191 * key[1]
    hashKey += 32767 * key[2]
    hashKey += 131071 * key[3]
    return hashKey

seen = set()
collisions = 0
for i in range(0,10000000):
    x = hash([random.randint(0,1000000), random.randint(0,10000), random.randint(0,1000), random.randint(0,1000)])
    if x in seen:
        collisions += 1
    else:
        seen.add(x)

print collisions

当我运行它时,它告诉我我遇到了 23735 次碰撞。 我还在 100 万个元素上进行了尝试,得到了 247 次碰撞。这两个数字都是 4 次运行的平均值。

【讨论】:

  • 很难看出这证明了什么,除了生日悖论。
  • @Hans:它准确地回答了原始问题;它还需要证明什么?
【解决方案3】:

我想说你应该使用

int hashKey = key[0].GetHashCode();
hashKey ^= key[1].GetHashCode();
hashKey ^= key[2].GetHashCode();
hashKey ^= key[3].GetHashCode();

因为它会给出更好的结果,但是当我测试它时,我感到非常惊讶。无论如何都要发布结果,因为作为一名科学家“你没想到的结果仍然是结果”。

Collisions1 是你的方法,Collisions2 是我的方法,这是 4 次运行的结果

Collisions1: 23744
Collisions2: 8996107

Collisions1: 23825
Collisions2: 8996215

Collisions1: 23771
Collisions2: 8996119

Collisions1: 24031
Collisions2: 8996157

【讨论】:

  • 想想 xor 对彼此接近的整数做了什么。这是否让您了解为什么会有如此多的碰撞? xor 几乎总是一个坏主意,因为它的输出只有在其操作数分布良好且不相关时才分布良好。一个好的散列函数应该会产生好的结果,即使它的输入是相关的。
  • @Eric Lippert,如果我没有在每个上调用 GetHashCode(),我会同意你的看法,但我假设(错误地)调用 GetHashCode() 会给我一个分布良好且不相关的结果.
  • 对 -- 一个 int 的 GetHashCode 只返回 int。
猜你喜欢
  • 2017-08-24
  • 2019-03-25
  • 1970-01-01
  • 1970-01-01
  • 2014-10-02
  • 2013-10-01
  • 2019-06-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多