对 C++ 哈希表有一个好的哈希函数吗?

Posted

技术标签:

【中文标题】对 C++ 哈希表有一个好的哈希函数吗?【英文标题】:Have a good hash function for a C++ hash table? 【发布时间】:2010-10-12 08:13:57 【问题描述】:

我需要一个 C++ 中面向性能的哈希函数实现,用于我将编码的哈希表。我已经环顾四周,只发现了一些问题,询问“一般”什么是好的散列函数。我考虑过 CRC32(但在哪里可以找到好的实现?)和一些密码算法。不过,我的桌子有非常具体的要求。

这就是桌子的样子:

100,000 items max
200,000 capacity (so the load is 0.5)
hashing a 6-character string which is a part of English sentence
     examples: "become"    "and he"    ", not "

我的哈希表的第一优先是快速搜索(检索)。快速插入并不重要,但它会伴随着快速搜索而来。删除并不重要,我不会研究重新散列。为了处理冲突,我可能会使用here 中描述的单独链接。我已经看过this article,但想听听那些曾经处理过此类任务的人的意见。

【问题讨论】:

我还添加了一个您可能喜欢的哈希函数作为另一个答案 如果你绝望了,为什么不为此提供代表赏金? rep bounty:如果没有人愿意提供有用的建议,我会提出,但我很惊喜 :) 无论如何,赏金的一个问题是您必须在 2 天后才能放置赏金 您是否考虑过使用以下一种或多种通用哈希函数:partow.net/programming/hashfunctions/index.html 它们非常快速且高效。 【参考方案1】:

现在假设您想要一个哈希,并且想要 快速 在您的情况下工作,因为您的字符串只有 6 个字符长,您可以使用这个魔法:

size_t precision = 2; //change the precision with this
size_t hash(const char* str)

   return (*(size_t*)str)>> precision;

CRC 用于慢动作 ;)

解释: 这通过将字符串指针的内容转换为“看起来像”一个 size_t(基于硬件的最佳匹配的 int32 或 int64)来工作。因此,字符串的内容被解释为原始数字,不再担心字符,然后您将其移位所需的精度(您将此数字调整为最佳性能,我发现 2 适用于散列字符串一套几千)。

另外,真正简洁的部分是现代硬件上的任何体面的编译器都会在 1 条汇编指令中散列这样的字符串,很难打败它;)

【讨论】:

哇..你能详细说明“((size_t)str)>>精度”的作用吗?它似乎做了一些我无法理解的奇怪的指针施法魔法。而且,“精度”是结果索引中的位数? 是的,精度是二进制位数 ZOMG ZOMG 谢谢!!!我正在使用此哈希函数和您在其他答案中概述的二叉树实现一个哈希表。 请注意,这不会像在 64 位硬件上那样工作,因为转换最终会使用 str[6] 和 str[7],它们不是字符串的一部分。此外,在 32 位硬件上,您只使用字符串中的前四个字符,因此您可能会遇到很多冲突。 我不明白这是一个好的算法。哈希输出非常线性地增加。根本没有雪崩效应……【参考方案2】:

这个简单的多项式效果惊人。我从 Microsoft Research 的 Paul Larson 那里得到它,他研究了各种散列函数和散列乘法器。

unsigned hash(const char* s, unsigned salt)

    unsigned h = salt;
    while (*s)
        h = h * 101 + (unsigned) *s++;
    return h;

salt 应该在创建哈希表之前初始化为某个随机选择的值以防御hash table attacks。如果这对您来说不是问题,请使用 0。

桌子的大小也很重要,以尽量减少冲突。听起来你的很好。

【讨论】:

如果你能保证你的字符串总是6个字符长,那么你可以尝试展开循环。 (unsigned char*) 我认为应该是 (unsigned char)。 sgraham:我在循环中将演员更改为(unsigned) 哈希表攻击链接现已断开。搬家了吗? 谢谢,文森特。我已经更新了我帖子的链接。我还更新了包含损坏链接的帖子本身。【参考方案3】:

Boost.Functional/Hash 可能对你有用。我没试过,所以我不能保证它的性能。

Boost 还有一个CRC library。

我会先查看Boost.Unordered(即 boost::unordered_map)。它对容器使用哈希映射而不是二叉树。

我相信一些 STL 实现在 stdext 命名空间中有一个 hash_map 容器。

【讨论】:

【参考方案4】:

您的表的大小将决定您应该使用什么大小的散列。当然,您希望尽量减少碰撞。我不确定您通过最大项目和容量指定的内容(它们对我来说似乎是同一件事)在任何情况下,这些数字中的任何一个都表明 32 位哈希就足够了。您可能会摆脱 CRC16(约 65,000 种可能性),但您可能需要处理很多冲突。另一方面,冲突的处理可能比 CRC32 散列更快。

我会说,选择 CRC32。您会发现不乏文档和示例代码。由于您已经确定了最大值并且速度是优先事项,因此请使用指针数组。使用哈希生成索引。碰撞时,增加索引直到你碰到一个空桶。快速简单。

【讨论】:

【参考方案5】:

由于您存储的是英文单词,因此您的大部分字符都是字母,并且数据的最高有效两位不会有太大变化。除此之外,我会保持非常简单,只需使用 XOR。毕竟,您不是在寻找加密强度,而只是在寻找合理均匀的分布。大致如下:

size_t hash(const std::string &data) 
  size_t h(0);
  for (int i=0; i<data.length(); i++)
    h = (h << 6) ^ (h >> 26) ^ data[i];
  
  return h;

除此之外,您是否将 std::tr1::hash 视为散列函数和/或将 std::tr1::unordered_map 视为散列表的实现?与实现自己的类相比,使用这些可能会节省大量工作。

【讨论】:

感谢您的建议!你能详细说明什么是“h = (h > 26) ^ data[i];”做?至于使用 c++ 库,我将无法使用,因为这是一个课堂练习...... ^ 是 XOR 的 C++ 运算符,> 是左右移位以“混合”一点...【参考方案6】:

我的哈希表的第一要务是快速搜索(检索)。

那么您使用的是正确的数据结构,因为在哈希表中搜索是 O(1)! :)

CRC32 应该没问题。实现并不复杂,它主要基于 XOR。只要确保它使用一个好的多项式即可。

【讨论】:

【参考方案7】:

来点简单的怎么样:

// Initialize hash lookup so that it maps the characters
// in your string to integers between 0 and 31
int hashLookup[256];

// Hash function for six character strings.
int hash(const char *str)

    int ret = 0, mult = 1;
    for (const char *p = str; *p; *p++, mult *= 32) 
        assert(*p >= 0 && *p < 256);
        ret += mult * hashLookup[*p];
    

    return ret;

这假定为 32 位整数。它每个字符使用 5 位,因此哈希值中只有 30 位。也许,您可以通过为前一个或两个字符生成六位来解决此问题。如果你的字符集足够小,你可能不需要超过 30 位。

【讨论】:

【参考方案8】:

如果您需要搜索短字符串并且插入不是问题,也许您可​​以使用 B-tree 或 2-3 树,在您的情况下通过散列不会获得太多收益。

这样做的方法是在每个节点中放置一个字母,因此首先检查节点“a”,然后检查“a”的子节点是否为“p”,它是子节点的“p”,然后是“l”,然后是“e”。在你有“apple”和“apply”的情况下,你需要寻找最后一个节点,(因为唯一的区别是最后一个“e”和“y”)

但是在大​​多数情况下,您只需几步即可获得单词(“xylophone”=>“x”->“ylophone”),因此您可以像这样优化。这可能比散列更快

【讨论】:

详细说明如何制作以6字符字符串为键的B-tree?谢谢! 还有一件事,它如何确定“x”之后的“ylophone”是唯一的孩子,所以它会分两步检索它?? 例如,我的结构是 char* data;字符链接'A', 'B', .., 'a', 'b', '', ..; 它将测试 root 是否 (node->link['x'] != NULL) 以获取以“x”开头的可能单词。 当您插入数据时,您需要对其进行“排序”。查找有关堆和优先级队列的信息。【参考方案9】:

自 C++11 起,C++ 提供了std::hash&lt; string &gt;( string )。这可能是一个高效的散列函数,可为大多数字符串提供good distribution of hash-codes。

此外,如果您正在考虑实现哈希表,您现在应该考虑改用 C++ std::unordered_map

【讨论】:

不,不是。它仅将字符串转换为 unsigned long。亲自尝试 std::hash(42) 返回的内容。不要问我,他们为什么提供这么无用的功能。 @Jay-Pi std::hash&lt;int&gt;(int)std::hash&lt;string&gt;(string) 是不同的函数。 比较 std::hash 和 stoul(l) 你会发现它们提供了相同的东西。我不清楚,为什么这没有在参考文献中记录。因为该函数不是散列,它只是创建“可散列的数据类型”。但是我不明白如何将其隐藏在模板后面使其更易于使用。

以上是关于对 C++ 哈希表有一个好的哈希函数吗?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何为 C++ 无序集定制我们自己的哈希函数以获得特定的顺序?

Python 散列表查询_进入<哈希函数;为结界的世界

C++进阶第二十一篇——哈希(概念+哈希函数+哈希冲突+哈希表+哈希桶+代码实现)

什么是好的哈希函数?

哈希函数详解(一)

C++:无法为 Vertex 对象创建哈希函数