我需要一个滚动哈希来在文件中搜索模式(我正在尝试使用Rabin-Karp字符串搜索算法)。
我了解什么是好的哈希函数以及好的滚动哈希函数应该如何工作,但我不知道如何有效地实现除法运算(或逆乘法),以便在滚动哈希时进行操作。我也读到rsync使用滚动版本的adler32,但那似乎不够随机。
理想情况下,如果你能指向一个经过优化的C/C++实现就最好了,但任何指向正确方向的指针都将有所帮助。
我需要一个滚动哈希来在文件中搜索模式(我正在尝试使用Rabin-Karp字符串搜索算法)。
我了解什么是好的哈希函数以及好的滚动哈希函数应该如何工作,但我不知道如何有效地实现除法运算(或逆乘法),以便在滚动哈希时进行操作。我也读到rsync使用滚动版本的adler32,但那似乎不够随机。
理想情况下,如果你能指向一个经过优化的C/C++实现就最好了,但任何指向正确方向的指针都将有所帮助。
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]
重要提示:您需要注意溢出问题。您可以选择让它溢出无符号整数,但我认为这更容易发生冲突(但速度更快!)
以下是我的实现:
#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;
}
快速实现的一些指针:
我之前写过这个程序。它是用C#编写的,但是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;
}