【问题标题】:Rolling hash in Rabin-KarpRabin-Karp 中的滚动哈希
【发布时间】:2013-12-23 03:01:13
【问题描述】:

我正在尝试实现 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合作

【问题讨论】:

标签: c++ c string algorithm hash


【解决方案1】:

哈希生成器代码错误。应该是

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

并取消注释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 开始以匹配您的生成器。

编辑: 我认为问题出在 pow 函数中。正如我在上面写的那样,它只溢出了 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;
}

【讨论】:

  • 修复哈希生成器并取消注释mod的行修复了向字符串添加新字符;但是你能解释一下前面的代码有什么问题吗?至于删除角色,那是行不通的。
  • @AdelQodmani 立即尝试删除。问题出在pow(257, str_len)。我会在回答中解释一下
  • 你指的是霍纳的方法,而不是赫伦的方法。也称为霍纳方案。
  • @AdelQodmani 是否因任何输入而损坏?你能发布更多的代码。我认为这可能与溢出问题有关
  • @janisz 我稍微编辑了这个问题,以反映我到底在做什么;到目前为止,您的建议不适用于删除字符。另一件事,做尽可能多的乘法不是一件好事吗?是的,慢;但它不会降低碰撞的机会吗?还是我做错了什么?是的,任何输入都会损坏。
猜你喜欢
  • 1970-01-01
  • 2017-07-27
  • 1970-01-01
  • 2023-03-09
  • 2012-01-18
  • 2021-07-03
  • 1970-01-01
  • 2016-09-06
  • 2018-11-22
相关资源
最近更新 更多