在哈希冲突和字符串性能方面的最佳哈希算法

时间:2008-10-30 19:05:49

标签: c# algorithm hash

如果我们具有以下优先级(按此顺序),那么最佳散列算法是什么:

  1. 最小哈希冲突
  2. 性能
  3. 它不必是安全的。基本上我正在尝试基于某些对象的属性组合创建索引。 所有属性都是字符串

    任何对c#实现的引用都将不胜感激。

9 个答案:

答案 0 :(得分:33)

忘记“最好”一词。无论任何人可能提出哪种哈希算法,除非你有一组非常有限的数据需要进行哈希处理,否则每个平均表现非常好的算法如果只是被正确地提供(或从你的角度来看)就会变得完全无用“错误的”数据。

而不是浪费太多时间考虑如何在不占用太多CPU时间的情况下使哈希更加无冲突,我宁愿开始考虑“如何使冲突更少问题”。例如。如果每个散列桶实际上都是一个表,并且该表中的所有字符串(具有冲突)按字母顺序排序,则可以使用二进制搜索(仅为O(log n))在存储桶表中进行搜索,这意味着,甚至当每个第二个哈希桶有4个冲突时,你的代码仍然会有不错的性能(与无冲突表相比会慢一点,但不是那么多)。这里的一个很大的优点是,如果你的表足够大并且你的哈希值不是太简单,那么产生相同哈希值的两个字符串通常看起来完全不同(因此二进制搜索可能会停止比较字符串之后平均可能有一个或两个字符;使每一次比较都非常快。)

实际上我之前有一种情况,在使用二进制搜索直接在排序表中搜索结果比散列更快!即使我的哈希算法很简单,也需要花费相当长的时间来对值进行哈希处理。性能测试表明,只有当我获得超过700-800个条目时,散列确实比二进制搜索更快。但是,由于表格永远不会超过256个条目,并且平均表格低于10个条目,基准测试清楚地表明,在每个系统上,每个CPU,二进制搜索都更快。在这里,通常已经比较数据的第一个字节的事实足以导致下一个bsearch迭代(因为数据在前一个到两个字节已经非常不同)已证明是一个很大的优势。

总结一下:我会采用一种不错的哈希算法,它不会导致平均过多的冲突而且速度相当快(我甚至会接受更多的冲突,如果它只是非常快!)而是优化我的代码如何在碰撞发生时获得最小的性能损失(并且它们将会!除非您的哈希空间至少等于或大于您的数据空间,并且您可以将唯一的哈希值映射到每个可能的数据集)。

答案 1 :(得分:17)

正如Nigel Campbell所示,没有“最佳”散列函数,因为它取决于您正在散列的数据特征以及是否需要加密质量哈希值。

那就是说,这里有一些指示:

  • 由于您用作哈希输入的项目只是一组字符串,因此您可以简单地组合每个字符串的哈希码。我已经看到以下伪代码建议这样做,但我不知道对它的任何特定分析:

    int hashCode = 0;
    
    foreach (string s in propertiesToHash) {
        hashCode = 31*hashCode + s.GetHashCode();
    }
    

    根据this article,System.Web有一个使用

    组合哈希码的内部方法
    combinedHash = ((combinedHash << 5) + combinedHash) ^ nextObj.GetHashCode();
    

    我也看到了代码只是xor的哈希码,但这对我来说似乎是一个坏主意(虽然我再也没有分析支持它)。如果没有别的,如果相同的字符串以不同的顺序进行哈希处理,则最终会发生冲突。

  • 我使用FNV效果很好:http://www.isthe.com/chongo/tech/comp/fnv/

  • Paul Hsieh有一篇不错的文章:http://www.azillionmonkeys.com/qed/hash.html

  • Bob Jenkins的另一篇好文章,最初于1997年在Dobb博士的杂志上发表(链接文章有更新):http://burtleburtle.net/bob/hash/doobs.html

答案 2 :(得分:8)

没有一种单一的最佳散列算法。如果您有一个已知的输入域,则可以使用完美散列生成器(如gperf)生成散列算法,该算法将在该特定输入集上获得100%的速率。否则,这个问题没有“正确”的答案。

答案 3 :(得分:8)

我会在这里跛脚,给出一个更理性的回答,而不是针对性的答案,但请把它的价值拿出来。

首先有两个不同的问题:

一个。碰撞概率 湾散列的性能(即:时间,cpu周期等)

这两个问题是温和的。它们并不完全相关。

问题是处理hashee和结果哈希空间之间的区别。当您散列1KB文件(1024字节)文件并且散列有32个字节时,将会:

1,0907481356194159294629842447338e + 2466(即2466个零的数字)输入文件的可能组合

并且哈希空间将具有

1,1579208923731619542357098500869e + 77(即77个零的数字)

差异很大。他们之间有2389个零区别。会有冲突(当两个不同的输入文件具有完全相同的哈希值时,冲突是一种特殊情况),因为我们将10 ^ 2466个案例减少到10 ^ 77个案例。

最小化碰撞风险的唯一方法是扩大哈希空间,从而使hahs更长。理想情况下,哈希将具有文件长度,但这在某种程度上是愚蠢的。


第二个问题是表现。这只涉及散列算法。当然,更长的哈希很可能需要更多的CPU周期,但更聪明的算法可能不需要。我对这个问题没有明确的案例答案。这太难了。

但是,您可以对不同的哈希实现进行基准测试,并从中得出预先得出的结论。

祝你好运;)

答案 4 :(得分:3)

Java的String类使用的简单hashCode可能会显示一个合适的算法。

下面是“GNU Classpath”实现。 (许可证:GPL)

  /**
   * Computes the hashcode for this String. This is done with int arithmetic,
   * where ** represents exponentiation, by this formula:<br>
   * <code>s[0]*31**(n-1) + s[1]*31**(n-2) + ... + s[n-1]</code>.
   *
   * @return hashcode value of this String
   */
  public int hashCode()
  {
    if (cachedHashCode != 0)
      return cachedHashCode;

    // Compute the hash code using a local variable to be reentrant.
    int hashCode = 0;
    int limit = count + offset;
    for (int i = offset; i < limit; i++)
      hashCode = hashCode * 31 + value[i];
    return cachedHashCode = hashCode;
  }

答案 5 :(得分:2)

您可以使用Knuth哈希函数described here来获取两者。

假设2的2次幂散列表大小非常快 - 只有一个乘法,一个移位,一个位和。更重要的是(对你而言)它最大限度地减少了碰撞(见this analysis)。

其他一些好的算法被描述为here

答案 6 :(得分:1)

我喜欢Stackoverflow!阅读这个问题让我更多地研究哈希函数,并找到了Cuckoo Hash

来自文章:

  

查询需要检查两个   哈希表中的位置   在最坏的情况下需要花费一些时间   (见Big O表示法)。这是在   与许多其他哈希表形成对比   算法,可能没有   当时不断恶化的情况   进行查找。

我认为这符合您的碰撞和表现标准。看起来权衡的是这种类型的哈希表只能满49%。

答案 7 :(得分:1)

以下是一种直接实现它的方法:http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

以下是该帖子的摘录:

如果我们有一个大写英文字母的字符集,那么字符集的长度是26,其中A可以用数字0表示,B表示数字1,C表示数字2,依此类推,直到Z现在,每当我们想要将这个字符集的字符串映射到一个唯一的数字时,我们就会执行与二进制格式相同的转换

答案 8 :(得分:1)

“Murmurhash”在性能和碰撞方面都非常出色。

“softwareengineering.stackexchange”中提到的帖子有一些测试,Murmur获胜。

我将自己的MurmurHash 2的C#端口写入.NET,并在466k英文单词列表上进行了测试,发生了22次碰撞。

结果和实现在这里:https://github.com/jitbit/MurmurHash.net(免责声明,我参与了这个开源项目!)