大字符串的 Rabin Karp 算法
Rabin Karp algorithm for big strings
我写了一个简单的子字符串搜索 Rabin-Karp 算法的逐步实现,它似乎工作正常,直到散列变得大于模数,然后就出错了...
这是代码,很简单:
typedef long long ll;
#define B 257
//base
#define M 2147483647
//modulus
//modulus for positive and negative values
ll mod(ll a){
return (a % M + M) % M;
}
//fast way to calculate modular power
ll power(ll n, ll e){
ll r = 1;
for(; e > 0; e >>= 1, n = (n*n) % M)
if(e&1) r = (r * n) % M;
return r;
}
//function to calculate de initial hash
//H(s) = s[0] * B^0 + s[1] * B^1 + ...
ll H(char sub[], int s){
ll h = 0;
for(ll i = 0; i < s; i++)
h = mod(h + mod(power(B, i) * sub[i]));
return h;
}
//brute force comparing when hashes match
bool check(char text[], char sub[], int ini, int s){
int i = 0;
while(text[ini + i] == sub[i] && i < s) i++;
return i == s;
}
//all together here
void RabinKarp(char text[], char sub[]){
int t = strlen(text), s = strlen(sub);
ll hs = H(sub, s), ht = H(text, s);
int lim = t - s;
for(int i = 0; i <= lim; i++){
if(ht == hs)
if(check(text, sub, i, s))
printf("MATCH AT %d\n", i);
ht -= text[i];
ht /= B;
ht = mod(ht + power(B, s - 1) * text[i + s]);
//we had text[i] * B^0 + text[i+1] * B^1 + ... + text[i + len - 1] * B^(len-1)
//then text[i+1] * B^1 + text[i+2] * B^2 + ... + text[i + len - 1] * B^(len-1)
//then text[i+1] * B^0 + text[i+2] * B^1 + ... + text[i + len - 1] * B^(len-2)
//finally we add a new last term text[i + len] * B^(len-1)
//so we moved the hash to the next position
}
}
int main(){
char text[] = "uvauvauvaaauva";
char sub[] = "uva";
char sub2[] = "uvauva";
RabinKarp(text, sub);
printf("----------------------------\n");
RabinKarp(text, sub2);
}
问题是,在我取模后,哈希值可能会变成一个小数,然后,当我向它添加一些大的因子时,即使它们应该匹配,哈希值也可能不匹配。
例如:abc inside xabc
我取abc和xab的hash时,假设它们都大于模数,所以模数运算后它们变小了。
然后,当我删除 'x' 并添加 'c' 因子时,总和可能小于模数但仍然很大,所以它不会匹配。
我该如何克服这个问题?
ht /= B;
是不合理的。首先,因为你在做算术 mod M,mod 等价的除法与标准除法不同。其次,因为您应该期望 x 和 x + M 的答案相同,而事实并非如此。
你有文字[i] * B^0 + 文字[i+1] * B^1 + ... + 文字[i + len - 1] * B^(len-1)
如果您与
一起工作
text[i] * B^(len-1) + text[i+1] * B^(len - 2) + ... + text[i + len - 1] * B^0
您可以减去 text[i] * B^(len-1) 然后乘以 B
我写了一个简单的子字符串搜索 Rabin-Karp 算法的逐步实现,它似乎工作正常,直到散列变得大于模数,然后就出错了...
这是代码,很简单:
typedef long long ll;
#define B 257
//base
#define M 2147483647
//modulus
//modulus for positive and negative values
ll mod(ll a){
return (a % M + M) % M;
}
//fast way to calculate modular power
ll power(ll n, ll e){
ll r = 1;
for(; e > 0; e >>= 1, n = (n*n) % M)
if(e&1) r = (r * n) % M;
return r;
}
//function to calculate de initial hash
//H(s) = s[0] * B^0 + s[1] * B^1 + ...
ll H(char sub[], int s){
ll h = 0;
for(ll i = 0; i < s; i++)
h = mod(h + mod(power(B, i) * sub[i]));
return h;
}
//brute force comparing when hashes match
bool check(char text[], char sub[], int ini, int s){
int i = 0;
while(text[ini + i] == sub[i] && i < s) i++;
return i == s;
}
//all together here
void RabinKarp(char text[], char sub[]){
int t = strlen(text), s = strlen(sub);
ll hs = H(sub, s), ht = H(text, s);
int lim = t - s;
for(int i = 0; i <= lim; i++){
if(ht == hs)
if(check(text, sub, i, s))
printf("MATCH AT %d\n", i);
ht -= text[i];
ht /= B;
ht = mod(ht + power(B, s - 1) * text[i + s]);
//we had text[i] * B^0 + text[i+1] * B^1 + ... + text[i + len - 1] * B^(len-1)
//then text[i+1] * B^1 + text[i+2] * B^2 + ... + text[i + len - 1] * B^(len-1)
//then text[i+1] * B^0 + text[i+2] * B^1 + ... + text[i + len - 1] * B^(len-2)
//finally we add a new last term text[i + len] * B^(len-1)
//so we moved the hash to the next position
}
}
int main(){
char text[] = "uvauvauvaaauva";
char sub[] = "uva";
char sub2[] = "uvauva";
RabinKarp(text, sub);
printf("----------------------------\n");
RabinKarp(text, sub2);
}
问题是,在我取模后,哈希值可能会变成一个小数,然后,当我向它添加一些大的因子时,即使它们应该匹配,哈希值也可能不匹配。
例如:abc inside xabc
我取abc和xab的hash时,假设它们都大于模数,所以模数运算后它们变小了。
然后,当我删除 'x' 并添加 'c' 因子时,总和可能小于模数但仍然很大,所以它不会匹配。
我该如何克服这个问题?
ht /= B; 是不合理的。首先,因为你在做算术 mod M,mod 等价的除法与标准除法不同。其次,因为您应该期望 x 和 x + M 的答案相同,而事实并非如此。
你有文字[i] * B^0 + 文字[i+1] * B^1 + ... + 文字[i + len - 1] * B^(len-1)
如果您与
一起工作text[i] * B^(len-1) + text[i+1] * B^(len - 2) + ... + text[i + len - 1] * B^0
您可以减去 text[i] * B^(len-1) 然后乘以 B