在Rabin-Karp滚动哈希

时间:2013-12-05 22:48:21

标签: c++ c string algorithm hash

我正在尝试使用Rabin-Karp来寻找子串;我被困在滚动哈希(试图使用formula suggested in Wikipedia)。

#define MOD 1000000007
unsigned long long rolling_hash(const char *str)
{
        unsigned long long hash = 0;
        size_t str_len = strlen(str);
        for(int i = 0, k = str_len -1; i < str_len; i++, k--) {
                hash = hash + str[i] * pow(257, k);
        //      hash = hash % MOD;
        }
        return hash;
}

int main(void)
{
        printf("%llu\n", rolling_hash("TestString"));
        printf("%llu\n", rolling_hash("estStringh"));
        unsigned long long old = rolling_hash("TestString");
        // Add a character to the end
        // since the last char in old was multiplied by 1, now multiply it by
        // the base and then add the _new_ character to the end
        old = old * 257 + 'h';
        //old = old % MOD;
        // Remove a char from the start
        // Simply, remove the hash value of the first character
        old = old - 'T' * pow(257, 10);;

        printf("\n%llu\n", old);
        return 0;
}

只要我不引入任何余数操作,上面的代码就可以完美地运行;一旦我取消注释我的%操作,事情就会崩溃,我从滚动哈希值的变化中得到的答案将不会等于第二次打印所打印的答案。

janisz的回答:
在janisz的答案中更改哈希生成器的建议使得剩余部分在添加新字符时起作用,但在删除旧字符时则不行。
注意:我正在使用自己的pow函数来处理unsigned long long

2 个答案:

答案 0 :(得分:2)

哈希发电机代码错了。它应该是

hash = (hash*257 + str[i]) % MOD;

和unncoment old_hash = old_hash % MOD;。还要更改从前一个

生成新哈希的方式
(old_hash - to_delete_char * pow(257, str_len-1)) % MOD;

看看你的代码。前两行非常好。循环中发生了什么。 首先,你正在做尽可能多的倍数。在我的方法中,我使用Horner scheme计算哈希因为哈希是一个多项式。

为什么它在没有模数且没有模数时有效。我认为这是一个巧合,因为你溢出整数有8个字符(log(2 ^ 64)/ log(257)= 8)。

现在删除字符有什么问题。 to_delete_char * pow(257, str_len);应该to_delete_char * pow(257, str_len-1);索引应该从0开始,而不是从1开始生成你的生成器。

修改 我认为问题出在战俘功能上。正如我上面所写,它溢出只有8个字符。在你的例子中,你有10个,所以它无法工作。

编辑:事实证明,添加和删除字符必须作为一个操作完成。可能由equivalents引起,但我不确定。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MOD 787

unsigned long long pow(int x, int y)
{
    unsigned long long ret = 1;
    for (int i=0;i<y;i++)
        ret = (ret*x)%MOD;
    return ret;
}
unsigned long long rolling_hash(const char *str)
{
        unsigned long long hash = 0;
        size_t str_len = strlen(str);
        for(int i = 0, k = str_len -1; i < str_len; i++, k--) {
                hash = hash + (str[i] * pow(257, k))%MOD;
                hash = hash % MOD;
        }
        return hash;
}

int main(void)
{
        char input[] = "TestString";
        printf("Input: %llu\n", rolling_hash(input));
        printf("Expected: %llu\n", rolling_hash("estStringh"));
        unsigned long long old = rolling_hash(input);
        // Add a character to the end
        // and Remove a char from the start

        unsigned long long  h = (input[0] * pow(257, strlen(input)))%MOD;
        old = ((old * 257) + 'h' - h) % MOD;

        printf("Actual: %llu\n", old);
        return 0;
}

答案 1 :(得分:-1)

我认为,使用pow()是缓慢且危险的,因为它返回double值,而对于长字符串,可能存在计算错误(双精度错误),并且减法值与添加完全不同。当字符串无法匹配时,这可能导致难以捉摸的错误。

我建议您使用循环移位和XOR。这些操作很快,没有“浮点/双精度误差”

uint32_t hash = 0;
// This is not changed during cycle, so can be computed once before search.
int rols = str_len & 31; 

添加哈希:

hash ^= ch;
hash = (hash << 1) | (hash >> 31);

从哈希中删除:

uint32_t x = ch;
x = (x << rols) | (x >> (32 - rols));
hash ^= x;

重要提示:添加后需要应用删除。