具有32位整数的低冲突率的快速字符串哈希算法

时间:2008-09-22 10:03:51

标签: c++ algorithm string hash

我有许多不相关的命名内容,我想快速搜索。 “aardvark”在任何地方始终都是“aardvark”,因此对字符串进行散列并重用整数可以很好地加速比较。整个名称集是未知的(并随着时间的推移而变化)。什么是快速字符串哈希算法,它将生成小(32或16)位值并具有低冲突率?

我希望看到一个特定于C / C ++的优化实现。

14 个答案:

答案 0 :(得分:32)

Murmur Hash非常好。

答案 1 :(得分:29)

其中一个FNV variants应符合您的要求。它们很快,并且产生相当均匀的分布式输出。

答案 2 :(得分:17)

对于固定的字符串集,请使用gperf。

如果您的字符串集发生了变化,您必须选择一个哈希函数。之前已讨论过该主题:

What's the best hashing algorithm to use on a stl string when using hash_map?

答案 3 :(得分:17)

nice article还有一个eternallyconfuzzled.com

詹金斯对字符串的一次性哈希应该是这样的:

#include <stdint.h>

uint32_t hash_string(const char * s)
{
    uint32_t hash = 0;

    for(; *s; ++s)
    {
        hash += *s;
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }

    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);

    return hash;
}

答案 4 :(得分:8)

根据您的使用案例,可能更好的另一种解决方案是 interned strings 。这就是符号如何工作,例如在Lisp。

interned string是一个字符串对象,其值是实际字符串字节的地址。因此,您通过检入全局表来创建一个实习字符串对象:如果字符串在那里,则将实习字符串初始化为该字符串的地址。如果没有,则插入它,然后初始化您的实习字符串。

这意味着从同一个字符串构建的两个实习字符串将具有相同的值,即一个地址。因此,如果N是系统中实习字符串的数量,则特征为:

  • 构造缓慢(需要查找和可能的内存分配)
  • 在并发线程的情况下需要全局数据和同步
  • 比较是O(1),因为你要比较的是地址,而不是实际的字符串字节(这意味着排序效果很好,但不会是字母排序)。

干杯,

卡尔

答案 5 :(得分:4)

为什么不使用Boost libraries?它们的散列函数使用起来很简单,Boost中的大部分内容很快就会成为C ++标准的一部分。其中一些已经是。

Boost哈希就像

一样简单
#include <boost/functional/hash.hpp>

int main()
{
    boost::hash<std::string> string_hash;

    std::size_t h = string_hash("Hash me");
}

您可以在boost.org

找到提升

答案 6 :(得分:4)

一个好主题永远不会迟到,我相信人们会对我的发现感兴趣。

我需要一个哈希函数,在阅读这篇文章并对这里给出的链接做了一些研究后,我想出了Daniel J Bernstein算法的这种变体,我曾经做过一个有趣的测试:

unsigned long djb_hashl(const char *clave)
{
    unsigned long c,i,h;

    for(i=h=0;clave[i];i++)
    {
        c = toupper(clave[i]);
        h = ((h << 5) + h) ^ c;
    }
    return h;
}

unsigned long djb_hashl(const char *clave) { unsigned long c,i,h; for(i=h=0;clave[i];i++) { c = toupper(clave[i]); h = ((h << 5) + h) ^ c; } return h; }

这种变化会使字符串忽略大小写,这符合我对哈希用户登录凭据的需要。 'clave'是西班牙语中的“关键”。我很抱歉西班牙语,但我的母语和程序都写在上面。

好吧,我编写了一个程序,它将从'test_aaaa'生成用户名到'test_zzzz',并且 - 为了使字符串更长 - 我在这个列表中添加了一个随机域:'cloud-nueve.com',' yahoo.com','gmail.com'和'hotmail.com'。因此,每个人看起来都像:


test_aaaa@cloud-nueve.com, test_aaab@yahoo.com, 
test_aaac@gmail.com, test_aaad@hotmail.com and so on.

以下是测试的输出-'Colision entre XXX y XXX'表示'XXX和XXX的碰撞'。 'palabras'表示'words','Total'在两种语言中都相同 - 。


    Buscando Colisiones...
    Colision entre 'test_phiz@hotmail.com' y 'test_juxg@cloud-nueve.com' (1DB903B7)
    Colision entre 'test_rfhh@hotmail.com' y 'test_fpgo@yahoo.com' (2F5BC088)
    Colision entre 'test_wxuj@hotmail.com' y 'test_pugy@cloud-nueve.com' (51FD09CC)
    Colision entre 'test_sctb@gmail.com' y 'test_iohw@cloud-nueve.com' (52F5480E)
    Colision entre 'test_wpgu@cloud-nueve.com' y 'test_seik@yahoo.com' (74FF72E2)
    Colision entre 'test_rfll@hotmail.com' y 'test_btgo@yahoo.com' (7FD70008)
    Colision entre 'test_wcho@cloud-nueve.com' y 'test_scfz@gmail.com' (9BD351C4)
    Colision entre 'test_swky@cloud-nueve.com' y 'test_fqpn@gmail.com' (A86953E1)
    Colision entre 'test_rftd@hotmail.com' y 'test_jlgo@yahoo.com' (BA6B0718)
    Colision entre 'test_rfpp@hotmail.com' y 'test_nxgo@yahoo.com' (D0523F88)
    Colision entre 'test_zlgo@yahoo.com' y 'test_rfdd@hotmail.com' (DEE08108)
    Total de Colisiones: 11
    Total de Palabras  : 456976

这还不错,456,976中的11次冲突(当然使用完整的32位作为表格长度)。

使用5个字符运行程序,即从'test_aaaaa'到'test_zzzzz',实际上耗尽了构建表的内存。以下是输出。 '没有干草memoria para insertar XXXX(insertadas XXX)'表示'没有留下内存插入XXX(插入XXX)'。基本上malloc()在那时失败了。


    No hay memoria para insertar 'test_epjcv' (insertadas 2097701).

    Buscando Colisiones...

    ...451 'colision' strings...

    Total de Colisiones: 451
    Total de Palabras  : 2097701

这意味着在2,097,701个字符串上只发生451次碰撞。请注意,在任何情况下,每个代码都有超过2次冲突。我确认它对我来说是一个很好的哈希,因为我需要的是将登录ID转换为40位唯一ID进行索引。因此,我使用它将登录凭据转换为32位散列,并使用额外的8位来处理每个代码最多255次冲突,这样几乎不可能生成测试结果。

希望这对某人有用。

修改

就像测试盒是AIX一样,我使用LDR_CNTRL = MAXDATA = 0x20000000来运行它以给它更多内存并且运行时间更长,结果在这里:

Buscando Colisiones ... Total de Colisiones:2908 Total de Palabras:5366384

在5,366,384次尝试之后是2908 !!

非常重要:使用-maix64编译程序(因此无符号长度为64位),所有情况下的冲突数为0!

答案 7 :(得分:3)

看看GNU gperf

答案 8 :(得分:3)

Hsieh哈希函数非常好,并且有一些基准/比较,作为C中的一般哈希函数。根据你想要的东西(它不是很明显)你可能想要考虑类似{{{ 3}}而不是。

答案 9 :(得分:3)

Bob Jenkins has many hash functions available,所有这些都很快且碰撞率很低。

答案 10 :(得分:2)

previous question

中有一些很好的讨论

关于如何选择哈希函数的概述,以及关于几个常见函数的分布的统计信息here

答案 11 :(得分:2)

您可以使用Reflector查看.NET在String.GetHashCode()方法中使用的内容。

我猜想微软花了相当多的时间来优化它。他们也在所有MSDN文档中打印过,它一直在变化。很明显,这是他们的“性能调整雷达”; - )

我会想到移植到C ++也是非常简单的。

答案 12 :(得分:0)

这里描述的是一种自己实现它的简单方法:http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

帖子的摘录:

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

答案 13 :(得分:-3)

CRC-32。谷歌上有大约一万亿个链接。