具有低碰撞率和32位整数的快速字符串哈希算法


68

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

我希望看到针对C / C ++的优化实现。


请添加关键字:哈希算法独特的低冲突
slashmais

24
下一页具有通用散列函数的几种实现,这些散列函数“性能良好”且具有较低的“冲突率”:partow.net/programming/hashfunctions/index.html

Answers:


30

其中的FNV变种应该满足你的要求。它们速度很快,并产生相当均匀的分布式输出。


如果您要使用FNV,请坚持使用FNV-1a,因为它在雪崩测试中具有可接受的结果(请参阅home.comcast.net/~bretm/hash/6.html)。或者只是使用MurmurHash2,它在速度和分发方面都更好(murmurhash.googlepages.com)。
史蒂文·苏迪特

7
@Steven:MurmurHash哈希仅被其作者分析了。我已经在几种不同的情况下使用了它,新版本的FNV似乎做得更好。

@sonicoder:虽然我不会超额出售MurmurHash,但普通的FNV非常糟糕,而FNV-1a则只能通过。碰巧的是,MurmurHash已被广泛分析并发现有用。它仍然不是加密哈希,无论如何都会发生冲突,但是与任何类型的FNV相比,它仍然是一个巨大的改进。
Steven Sudit

7
@Steven Sudit:正如我所说,它的作者“仅”对其进行了分析,没有其他人进行过分析。因此,“分析”的结果并不是很客观。

5
@sonicoder:我会说得更直白:不,你错了。许多第三方(包括学术机构)对此进行了分析。访问Wikipedia以获得链接。更重要的是,它不仅在总体上做得很好,而且通过创建MurmurHash3解决了所发现的特定缺陷。
史蒂文·苏迪特



17

eternallyconfuzzled.com上也有一篇不错的文章

詹金斯(Jenkins)的一次性字符串哈希应如下所示:

#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;
}

8

根据您的用例,可能会更好的另一种解决方案是interstrings。这就是符号在Lisp中的工作方式。

插入字符串是一个字符串对象,其值是实际字符串字节的地址。因此,您可以通过检入一个全局表来创建一个嵌入的字符串对象:如果该字符串在其中,则可以将嵌入的字符串初始化为该字符串的地址。如果不是,则将其插入,然后初始化您的实习字符串。

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

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

干杯,

卡尔


4

您为什么不只使用Boost库?它们的哈希函数易于使用,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上找到boost


4
STL和boost tr1的字符串哈希函数都非常弱。
obecalp

4

对于一个好的学科来说永远不会迟到,我相信人们会对我的发现感兴趣。

我需要一个哈希函数,在阅读了这篇文章并对此处给出的链接进行了一些研究之后,我想到了丹尼尔·J·伯恩斯坦算法的这种变体,我曾经做过一个有趣的测试:

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等。

这是测试的输出-“ Colision entre XXX y XXX”表示“ XXX和XXX的碰撞”。“ palabras”表示两种语言中的“单词”和“总计”相同。

    Buscando碰撞...
    碰撞入口'test_phiz@hotmail.com'和'test_juxg@cloud-nueve.com'(1DB903B7)
    碰撞入口'test_rfhh@hotmail.com'和'test_fpgo@yahoo.com'(2F5BC088)
    碰撞入口'test_wxuj@hotmail.com'和'test_pugy@cloud-nueve.com'(51FD09CC)
    碰撞入口'test_sctb@gmail.com'和'test_iohw@cloud-nueve.com'(52F5480E)
    碰撞入口'test_wpgu@cloud-nueve.com'和'test_seik@yahoo.com'(74FF72E2)
    碰撞入口'test_rfll@hotmail.com'和'test_btgo@yahoo.com'(7FD70008)
    碰撞入口'test_wcho@cloud-nueve.com'和'test_scfz@gmail.com'(9BD351C4)
    碰撞入口'test_swky@cloud-nueve.com'和'test_fqpn@gmail.com'(A86953E1)
    碰撞入口'test_rftd@hotmail.com'和'test_jlgo@yahoo.com'(BA6B0718)
    碰撞输入'test_rfpp@hotmail.com'和'test_nxgo@yahoo.com'(D0523F88)
    碰撞入口'test_zlgo@yahoo.com'和'test_rfdd@hotmail.com'(DEE08108)
    Collisiones总数:11
    巴拉布拉斯总数:456976

不错,在456,976个碰撞中有11个碰撞(当然,使用完整的32位作为表长度)。

使用5个字符(即从“ test_aaaaa”到“ test_zzzzz”)运行程序实际上会耗尽构建表的内存。以下是输出。“没有干草记忆插入XXXX(插入XXX)”表示“没有剩余空间可插入XXX(插入XXX)”。基本上,malloc()在那时失败了。

    没有干草记忆插入器'test_epjcv'(insertadas 2097701)。

    Buscando碰撞...

    451个'collision'字符串...

    冲突总数:451
    巴拉布拉斯总数:2097701

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

希望这对某人有用。

编辑:

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

Buscando Colisiones ...总数:2908总数:Palabras:5366384

经过5,366,384次尝试,即为2908!

非常重要:使用-maix64编译程序(因此unsigned long为64位),在所有情况下冲突次数均为0!


3

哈希函数是相当不错的,并且具有一定的基准测试/对比,因为根据你想要什么C.一般散列函数(它不是完全明显),你可能要考虑像国开行代替。




2

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

我可能会猜测微软会花费大量时间对此进行优化。他们还在所有MSDN文档中印刷了该文档,并且随时可能更改。显然,这是在他们的“性能调整雷达”上;-)

我也想过,移植到C ++会很简单。



0

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

帖子摘录:

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


(32 or 16) bit values给定字符集(例如,从24到1.111.998个代码点),它(给定显示链接内容的超文本浏览器)如何映射到?基本转换不是有用的哈希函数。
greybeard 2015年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.