分析哈希函数中素数的用法

时间:2011-03-07 20:24:20

标签: c++ hashtable hash

我正在研究基于散列的排序,我发现在哈希函数中使用素数被认为是一个好主意,因为将密钥的每个字符乘以素数并将结果相加会产生唯一值(因为素数是唯一的)而像31这样的素数会产生更好的键分布。

key(s)=s[0]*31(len–1)+s[1]*31(len–2)+ ... +s[len–1]

示例代码:

public int hashCode( ) 
{
    int h = hash;
    if (h == 0) 
    {
        for (int i = 0; i < chars.length; i++) 
        {
            h = MULT*h + chars[i];
        }
        hash = h;
    }
    return h;
}

我想理解为什么在下面这个解释的背景下使用偶数乘以每个字符是一个坏主意(在另一个论坛上找到;它听起来像一个很好的解释,但我没有把握它)。如果下面的推理无效,我将不胜感激,我将不胜感激。

  

假设MULT是26,并考虑   哈希一百个字符的字符串。   弦的影响有多大   第一个角色在决赛中   'h'的价值?第一个角色的价值   将乘以MULT 99   时间,所以如果算术完成了   以无限的精度值   由一些混乱的比特组成   接着是99个低阶零位 -   每次你乘以MULT你   引入另一个低阶零,   对?电脑是有限的   算术只是砍掉所有的   多余的高位,所以第一个   角色对'h'的实际贡献   是...正是零! 'h'值   仅取决于最右边的32   字符串字符(假设为32位   int),即使那时事情也没有   精彩:最后的32个中的第一个   字节只影响最左边的位   “h”并没有影响   剩下的31.显然,是一个有价值的   MULT是一个糟糕的主意。

4 个答案:

答案 0 :(得分:2)

我认为更容易看出你是否使用2而不是26.它们对h的最低位具有相同的效果。考虑一个33字符的字符串,其中包含一些字符c,后跟32个零字节(用于说明目的)。由于字符串不是完全为null,因此您希望哈希值非零。

对于第一个字符,您计算的哈希h等于c[0]。对于第二个字符,您可以h * 2 + c[1]。现在h2*c[0]。对于第三个字符h现在是h*2 + c[2],它可以用于4*c[0]。再重复30次,您可以看到乘数使用的位数多于目的地中可用的位数,这意味着c[0]对最终散列没有任何影响。

结束数学与26之类的不同乘数完全相同,除了中间哈希在过程中经常模数2^32。由于26是偶数,所以每次迭代仍然会向低端添加一个0位。

答案 1 :(得分:1)

这个哈希可以这样描述(这里^是取幂,而不是xor)。

hash(string) = sum_over_i(s[i] * MULT^(strlen(s) - i - 1)) % (2^32).

看看第一个角色的贡献。这是

(s[0] * MULT^(strlen(s) - 1)) % (2^32).

如果字符串足够长(strlen(s)> 32),那么这就是零。

答案 2 :(得分:1)

其他人已经发布了答案 - 如果你使用偶数倍,那么只有字符串中的最后一个字符才能计算哈希,因为早期字符的影响将从寄存器中移出。

现在让我们考虑当你使用31这样的乘数时会发生什么。好吧,31是32-1或2 ^ 5 - 1.所以当你使用它时,你的最终哈希值将是:

\ sum {c_i 2 ^ {5(len-i)} - \ sum {c_i}

不幸的是,stackoverflow并没有强调TeX数学符号,所以上面的内容很难理解,但它对字符串中的字符进行了两次求和,其中第一个字符串将字符串中的每个后续字符的每个字符移位5位。所以使用32位机器,除了字符串的最后七个字符外,它将从顶部移开。

这样做的结果是使用31的乘数意味着虽然最后七个字符以外的字符对字符串有影响,但它完全独立于它们的顺序。如果你拿两个具有相同最后7个字符的字符串,其他字符也相同但顺序不同,那么你将获得两个相同的哈希值。对于像“az”和“by”之类的东西,除了最后7个字符之外,你也会获得相同的哈希值。

因此,使用素数乘数虽然比偶数乘数好得多,但仍然不是很好。更好的是使用旋转指令,当它们从顶部移出时将位移回底部。类似的东西:

public unisgned hashCode(string chars)
{
    unsigned h = 0;
    for (int i = 0; i < chars.length; i++) {
        h = (h<<5) + (h>>27);  // ROL by 5, assuming 32 bits here
        h += chars[i];
    }
    return h;
}

当然,这取决于您的编译器是否足够聪明,能够识别旋转指令的惯用语并将其转换为单个指令以实现最高效率。

这仍然存在这样的问题:在字符串中交换32个字符的块将提供相同的哈希值,因此它远非强大,但可能足以满足大多数非加密目的

答案 3 :(得分:0)

  

会产生一个独特的价值

停在那儿。哈希不是唯一的。一个好的哈希算法可以最大限度地减少冲突,但是鸽子原理确保我们无法完全避免冲突(对于任何具有非平凡信息内容的数据类型)。