【问题标题】:Hash function for clustered integers聚集整数的散列函数
【发布时间】:2016-02-14 09:00:40
【问题描述】:

我正在尝试设置一个哈希表(在 C++ 中,使用 unordered_map 容器),其中包含 1875 个整数项目,这些项目随机分布在 0 到 4891 的区间内。现在我的问题是这个区间内的分布是不是统一的,而是看起来像这样:

其中 1875 个随机整数中的每一个都被绘制为一个点,其中 x 对应于整数值,y = 1(以便可视化分布)。

显然,分布是这样的,即在没有随机整数的地方存在很大的差距。如果我使用恒等函数作为我的散列函数(即使用随机整数本身作为散列值),我得到 714 个空桶、814 个带有单个元素的桶、499 个带有 2 个元素的桶和 21 个带有 3 个或更多元素的桶。

我使用的是英特尔 C++ 编译器,它使用 2 的幂来计算哈希表中的桶数。就我而言,现在哈希表有 2^11 = 2048 个桶。

对于这种情况,什么是好的散列函数?我的理解是,在这种情况下,一个好的哈希函数会摆脱这些聚集的整数,并以更均匀的分布将它们打乱,但是如何实现呢?

【问题讨论】:

  • 所以你只有 1800 个整数?对 1800 个键值对的排序 std::vector 然后进行二进制搜索怎么样?这至少值得衡量。
  • Boost 的flat_map (overview) (API docs) 是@BaummitAugen 建议的一个很好的完整实现——它的接口就像一个unordered_map,但它的实现就像一个有序向量。
  • @user3208430,我来了——各种关联容器的here's a benchmark,与用于随机搜索的 flat_map 相比,unordered_map 做得非常好(尽管 flat_map 用于迭代)。跨度>
  • 存储桶负载的分布实际上优于任何标准哈希函数。一个选项可以是无符号乘以一个(大)奇数。
  • 有4891个桶,用恒等函数作为哈希函数怎么样。它可能会占用更少的空间,因为您不需要指针和溢出链以及所有这些东西。 (它被称为索引)并且 30% 的负载因子并不是那么奇怪,如果它保证你完美 hashing ...

标签: c++ hash


【解决方案1】:

我发现 Pearson 的哈希函数是获得随机性的绝佳方法:

https://en.wikipedia.org/wiki/Pearson_hashing

基本上,这个想法是默认情况下它会生成一堆非常随机的数字到一个由 256 个 bin 组成的数组中,但您可以将其修改为 1800 适合你的场景。重要的是数组足够小以适合内存。

【讨论】:

    【解决方案2】:

    如果您需要减少冲突的数量,查看像 cuckoo hashing 这样的专用散列方案可能会有所帮助。本质上,您通过多个哈希函数摊销以保持O(1) 的复杂性。

    如果冲突成本低(例如,它们适合缓存行或它们是可预测的),无论冲突的渐近成本如何,您仍然可能会看到更好的性能。

    出于这个原因倾向于使用扁平结构,因为它们具有良好的缓存特性。这也是在性能很重要时它们往往会受到青睐的原因之一。

    【讨论】:

      【解决方案3】:

      所以我花了一些时间尝试不同的东西。到目前为止,这是我的结论。

      首先,必须意识到将 1875 个元素放入具有 2048 个桶的哈希表中可能会产生相当多的冲突。确实,如果考虑到每个元素被分配到 2048 个桶中的任何一个桶的概率相等,那么预期的碰撞次数是 646 次(通过类似于所谓的生日问题的论点,参见https://math.stackexchange.com/questions/35791/birthday-problem-expected-number-of-collisions?rq=1,公式预计 nb. of collisions = n – N * (1 – (1 – 1/N)^n) 其中 n 是元素的数量,N 是桶的数量)。例如,如果 1875 个元素是在 [0, 2047] 区间内随机选择允许重复,或者如果 1875 个元素是在相对于数量非常大的区间内随机选择的桶 2048 有或没有重复

      考虑到这一点,使用身份函数作为哈希函数(参见原始问题)获得的 541 次冲突似乎并不算太​​糟糕。尽管分布中有很大差距,但碰撞次数比均匀分布情况下的要少的原因是,根据问题的性质,1875 个元素具有不同的值,因此只有大于 2048 的元素才会导致碰撞,因为它们是使用模运算符环绕。

      现在,我们知道将输入区间 [0, 4891] 随机且均匀地映射到更大的区间(例如 32 位整数)的哈希函数是不可取的,因为它会导致比身份更多的冲突哈希函数。然而,人们可能想知道是否有可能从输入区间 [0, 4891] 到某个不太大的区间(它可能是相同的 [0, 4891] 区间或任何其他区间,如 [0 , 2048], [0, 5000] 等),这将减少碰撞。我按照 rts1 的建议尝试了类似 Pearson 的映射,但发现它并没有提高碰撞次数。

      到目前为止,我已经决定使用的只是身份函数作为哈希函数,并确保我的元素数量不会太接近我的桶数(元素数量的 1.5 倍似乎相当合理桶的数量)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-14
        • 2013-07-20
        • 2011-05-09
        • 2014-10-28
        • 2014-01-02
        • 2016-11-07
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多