【问题标题】:Fast implementation of Rolling hashRolling hash的快速实现
【发布时间】:2010-10-17 05:47:15
【问题描述】:

我需要一个滚动哈希来搜索文件中的模式。 (我正在尝试使用Rabin-Karp string search algorithm)。

我了解一个好的 Hash 如何工作以及一个好的 Rolling Hash 应该如何工作,但我无法弄清楚如何有效地实现 divide em> (或逆乘法)滚动散列时。我还阅读了 rsync 使用 adler32 的滚动版本,但这看起来不像是一个足够随机的散列。

理想情况下,如果您能指出一个优化的 C/C++ 实现,那就太好了,但是任何指向正确方向的指针都会有所帮助。

【问题讨论】:

  • 适用于通过搜索滚动散列和乘法逆运算到达这里的任何人。如果你的滚动哈希实现需要支持可变长度,你只需要除法(或使用乘法逆运算),如果你想做 Rabin-Karp,你可能不需要这个。关于如何在这个video 中使用逆的一些指示以及我在python 中的尝试实现。

标签: c++ hash


【解决方案1】:

Cipher 的“prime base”想法应该可以正常工作 - 尽管他发布的解决方案看起来有点粗略。

我认为这种方法不需要逆乘法。 这是我的解决方案:

假设我们当前散列的字符串是“abc”,我们想追加“d”并删除“a”。

就像 Cipher 一样,我的基本哈希算法是:

unsigned hash(const string& s)
{
    unsigned ret = 0;
    for (int i = 0; i < s.size(); i++)
    {
        ret *= PRIME_BASE; //shift over by one
        ret += s[i]; //add the current char
        ret %= PRIME_MOD; //don't overflow
    }
    return ret;
}

现在,实现滑动:

hash1 = [0]*base^(n-1) + [1]*base^(n-2) + ... + [n-1]

我们想在最后添加一些东西并删除第一个值,所以

hash2 = [1]*base^(n-1) + [2]*base^(n-2) + ... + [n]

首先我们可以添加最后一个字母:

hash2 = (hash1 * PRIME_BASE) + newchar;
=> [0]*base^n + [1]*base^(n-1) + ... + [n-1]*base + [n]

然后简单地减去第一个字符:

hash2 -= firstchar * pow(base, n);
=> [1]*base^(n-1) + ... + [n]

重要提示:您必须小心溢出。你可以选择让它溢出 unsigned int,但我认为它更容易发生冲突(但也更快!)

这是我的实现:

#include <iostream>
#include <string>
using namespace std;

const unsigned PRIME_BASE = 257;
const unsigned PRIME_MOD = 1000000007;

unsigned hash(const string& s)
{
    long long ret = 0;
    for (int i = 0; i < s.size(); i++)
    {
        ret = ret*PRIME_BASE + s[i];
        ret %= PRIME_MOD; //don't overflow
    }
    return ret;
}

int rabin_karp(const string& needle, const string& haystack)
{
    //I'm using long longs to avoid overflow
    long long hash1 = hash(needle);
    long long hash2 = 0;

    //you could use exponentiation by squaring for extra speed
    long long power = 1;
    for (int i = 0; i < needle.size(); i++)
        power = (power * PRIME_BASE) % PRIME_MOD;

    for (int i = 0; i < haystack.size(); i++)
    {
        //add the last letter
        hash2 = hash2*PRIME_BASE + haystack[i];
        hash2 %= PRIME_MOD;

        //remove the first character, if needed
        if (i >= needle.size())
        {
            hash2 -= power * haystack[i-needle.size()] % PRIME_MOD;
            if (hash2 < 0) //negative can be made positive with mod
                hash2 += PRIME_MOD;
        }

        //match?
        if (i >= needle.size()-1 && hash1 == hash2)
            return i - (needle.size()-1);
    }

    return -1;
}

int main()
{
    cout << rabin_karp("waldo", "willy werther warhol wendy --> waldo <--") << endl;
}

【讨论】:

  • @community 为什么 MOD 应该是质数。你能给我一些我可以检查的来源吗?因为在这里:stackoverflow.com/questions/5835946/… 我们对这个话题进行了一次大讨论,无法得出一个意见。
【解决方案2】:

一些快速实现的指针:

  1. 避免取模运算(在类似 C 的语言中为 %)使用掩码 n - 1,其中 n 为 2^k,包括哈希表查找操作。是的,可以使用非素数模数产生良好的哈希值。
  2. 选择具有良好品质因数的乘数和指数,详情请参阅this paper

【讨论】:

    【解决方案3】:

    这是我不久前写的。它是用 c# 编写的,但它与 c 非常接近,您只需添加几个参数。这应该工作,但我没有测试这个版本,我删除了几行会忽略大小写或非单词字符。我希望这会有所帮助

    private const int primeBase = 101;
    //primeBase^2*[0]+primeBase^1*[1]+primeBase^0*[2]
    //==
    //primeBase*(primeBase*[0]+[1])+[2]
    public static int primeRollingHash(String input, int start, int end)
    {
        int acc = 0;
        for (int i = start; i <= end; i++)
        {
            char c = input[i];
            acc *= primeBase;
            acc += c;
        }
        return acc;
    }
    
    public static int primeRollingHash(String input)
    {
        return primeRollingHash(input, 0, input.Length - 1);
    }
    
    public static int rollHashRight(int currentHashValue, String input, 
                                    int start, int newEnd)
    {
        if (newEnd == input.Length)
            return currentHashValue;
        int length = newEnd - start - 1;
        int multiplier = primeBase;
        char newChar = input[newEnd];
        int firstValue = input[start];
        if(length>0)
            firstValue *= length * primeBase;
        return (currentHashValue - firstValue) * multiplier + newChar;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多