【问题标题】:Perfect hash function for a set of integers with no updates一组没有更新的整数的完美哈希函数
【发布时间】:2010-11-19 13:26:09
【问题描述】:

在我从事的一个应用程序中,有必要具有这样的功能:

bool IsInList(int iTest)
{
   //Return if iTest appears in a set of numbers.
}

编号列表在应用程序加载时是已知的(但在同一应用程序的两个实例之间并不总是相同)并且不会在整个程序中更改(或添加到)。整数本身可能很大并且范围很大,因此拥有vector<bool> 效率不高。性能是一个问题,因为该功能处于热点。我听说过 Perfect hashing,但找不到任何好的建议。任何指针都会有所帮助。谢谢。

附言如果解决方案不是第三方库,我会很理想,因为我不能在这里使用它们。如果可能的话,一些简单到可以理解和手动实现的东西会很棒。

【问题讨论】:

  • 我也在尝试了解字符串到数字的完美散列。但是只找到很少的信息:(
  • 你确定 O(log n) 查找(例如对排序数组的二进制搜索)不够快吗?请记住散列是 O(k),其中 k 与 n 无关(因此散列在 n 方面是 O(1))但对于非巨大的 n 值仍然可以大于 O(log n)(即少于一百万)。
  • Here 是最小完美哈希查找器的 Java 实现。
  • @Pointy:有趣的是,它调用 C++ 的 OO 特性很奇怪,那么它使用 Java 的四个原因中有两个是 OO。
  • @Fred Nunk 是的,我没有过多关注迷你咆哮;我只是看了一下代码,这似乎至少基本能胜任 - 加上他引用了一些看起来很有趣的论文

标签: c++ performance hash


【解决方案1】:

我建议将Bloom Filters 与简单的std::map 结合使用。

不幸的是,布隆过滤器不是标准库的一部分,因此您必须自己实现它。然而事实证明这是一个相当简单的结构!

布隆过滤器是一种专门解决以下问题的数据结构:此元素是否是集合的一部分,但这样做的内存要求非常严格,而且速度也很快。

稍微有点问题的是答案是……特别的:这个元素是集合的一部分吗?

  • 没有
  • 可能(具有给定概率,具体取决于布隆过滤器的属性)

在您查看实现之前,这看起来很奇怪,并且可能需要一些调整(有几个属性)来降低概率,但是......

对您来说真正有趣的是,对于它回答 的所有情况,您可以保证它不是集合的一部分。

因此,Bloom Filter 非常适合作为二叉树或哈希映射的门卫。仔细调整它只会让很少的误报通过。例如,gcc uses one

【讨论】:

  • 一般来说,一个排序的std::vectorstd::map 有更好的性能,当集合只被构建一次并且之后才被引用。 std::vector 具有极好的缓存位置,std::lower_boundstd::map::find 一样快。
  • @deft_code:我完全同意(实际上我自己通常使用排序数组/向量作为常量)。这不仅是缓存局部性,还有缓存预取在工作,因为内存是连续的,而树中的访问则更不可预测(对于 CPU)。
【解决方案2】:

完美的散列函数将一组输入映射到没有冲突的整数上。鉴于您的输入是一组整数,这些值本身就是一个完美的哈希函数。这真的与手头的问题无关。

用于测试存在性的最明显且易于实现的解决方案是排序列表或平衡二叉树。然后你可以在 log(N) 时间内决定存在。我怀疑它会变得更好。

【讨论】:

  • 你写的是真的,但是仍然存在 minimal 完美散列函数会有用的情况,如果找到该函数的成本不太高,那么一个 O (1) 解决方案将产生。
  • 如果你使用源整数作为哈希值,你必须使用一个非常大的数组,其中可能有很多未使用的项目,因为他说范围很大。
  • @Johannes Schaub 这就是为什么人们会寻找一个 minimal 完美散列,这是一个产生等于 number 范围内的值的散列不同的集合成员,独立于原始范围。
  • @Pointy 是的,但它不使用源整数作为哈希值。我的评论是给@Donnie
  • @Donnie 这实际上不是真的 - minimal 哈希将源整数集映射到最小范围的值。如果原始集合中有 n 个不同的值(分布在任意范围内),则 最小完美散列 会生成 [0 ... 范围内的整数n )
【解决方案3】:

我想到的是gperf。但是,它基于字符串而不是数字。但是,可以调整部分计算以使用数字作为哈希生成器的输入。

【讨论】:

    【解决方案4】:

    整数,字符串,无所谓

    http://videolectures.net/mit6046jf05_leiserson_lec08/

    在介绍之后,在 49:38,您将学习如何执行此操作。演示了点积哈希函数,因为它有一个优雅的证明。大多数哈希函数就像伏都教黑魔法。不要在这里浪费时间,找到适合您的数据类型的 FAST 并且为散列提供一些可调整的 SEED 的东西。一个好的组合比增加哈希表的替代方法更好。

    @54:30 教授描绘了一种进行完美哈希的标准方法。完美的最小散列超出了本次讲座的范围。 (祝你好运!)

    这完全取决于你的模组。

    请记住,他展示的分析可以通过了解您正在运行的硬件来进一步优化。

    std::map 可以在 99.9% 的场景中获得非常好的性能。如果您的热点多次具有相同的 iTest,请将地图结果与临时哈希缓存相结合。

    Int 是可以做的数据类型之一:

    bool hash[UINT_MAX]; // stackoverflow ;)
    

    然后把它填满。如果你不关心负数,那么它就简单了两倍。

    【讨论】:

      【解决方案5】:

      对于这个问题,我会使用二进制搜索,假设可以保持数字列表的排序。

      维基百科有example implementations,应该很简单,可以翻译成C++。

      【讨论】:

        【解决方案6】:

        将 N 个不同的随机分散整数映射到 N 个连续的桶(即完美的最小哈希)是没有必要或不切实际的,重要的是确定一个可接受的比率。要在运行时执行此操作,您可以首先配置一个可接受的最差比率(例如 1 到 20)和一个没有比这个更好的比率(例如 1 到 4),然后随机变化 (例如,改变使用的素数)一种快速计算的哈希算法,看看你能多么容易地满足越来越困难的比率。对于最坏的可接受的情况,您不会超时,或者您依赖于速度较慢但可靠的东西(用于解决冲突的容器或置换列表)。然后,每增加一个 X%,允许一秒钟或十次(可配置),直到您无法以该比率取得成功或达到无品脱更好的比率....

        每个人都清楚,这适用于仅在运行时知道的输入,而事先不知道有用的模式,这就是为什么必须在运行时试验或积极派生不同的哈希函数的原因。简单地说“整数输入形成哈希”是不可接受的,因为当 %-ed 进入任何合理的数组大小时都会发生冲突。但是,您也不需要以完美包装的数组为目标。还要记住,您可以拥有一个指向压缩数组的稀疏指针数组,因此对于大对象来说几乎没有内存浪费。

        【讨论】:

          【解决方案7】:

          Original Question

          在使用了一段时间后,我想出了一些哈希函数,这些函数似乎在字符串上运行得相当好,从而产生了独特而完美的哈希。

          假设数组中的值范围从 L 到 H。这会产生一个范围 R = H - L + 1。 一般都挺大的。

          然后我将模运算符从 H 向下应用到 L + 1,寻找一种使它们保持唯一但范围更小的映射。

          在您的情况下,您使用的是整数。从技术上讲,它们已经经过哈希处理,但范围很大。

          可能你可以得到你想要的,只需应用模数运算符。 可能你需要先在它前面放一个哈希函数。

          也可能是您找不到完美的哈希值,在这种情况下,您的容器类应该有一个后备位置....二进制搜索,或映射或类似的东西,这样 您可以保证容器在所有情况下都能正常工作。

          【讨论】:

            【解决方案8】:

            trie 或者 van Emde Boas 树可能是一个更好的选择,它可以创建一个节省空间的整数集,其查找时间对数据结构中的对象数量保持不变,假设即使 std::bitset 也是如此大。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-05-22
              • 1970-01-01
              • 2022-11-02
              • 1970-01-01
              相关资源
              最近更新 更多