1.背景
1.1 算法简介:M.O.Rabin和R.A.Karp发明了一种基于散列的字符串查找算法。我们只需要计算模式串的散列函数,然后利用相同的散列函数计算文本中所有可能的M个字符的子字符串散列值并寻找匹配。如果找到了一个散列值和模式字符串相同的子字符串,再继续验证是否相同。这是一个有趣的算法,重点不在于其只用线性时间解决问题,而在于其对散列技术的使用,这是一个具有启发性的算法!
1.2 结论:Rabin-Karp算法优点是运行速度为线性级别,缺点是内循环很长。(若干次算术运算,而其他算法都只需比较字符)
2.思想
2.1 基本思想:长度为M的模式串对应着一个R进制的M位数。使用除留余数法构造一个能够将 R 进制的 M 位数转化为一个0到Q-1之间的 int 值的散列函数。我们在不溢出的情况下选择一个尽可能大的随机素数Q(因为我们并不真正需要一张散列表,故Q越大越好,冲突越少)。接下来对文本所有长度为M的子串计算散列值并寻找匹配。
2.2 计算散列函数:思想很简单,只需将所有子串散列值求出,一个一个匹配即可,但是如果离线计算每个子串的散列值,时间复杂度与 M*N 成正比,故问题变成了如何在线性时间求出散列值。
2.3 关键思想:既然子串长度确定为M(即模式串的长度M),那我们每次只需向后移动一位,将第一个元素去掉,最后一个元素加上即可更新散列值,再与模式串散列值比较即可。在这种情况下时间复杂度与N成正比。
3.实现
3.1 具体步骤:
- 得到模式串长度M
- 选择一个恰当的R,尽可能大的随机素数Q,构造散列函数
- 计算出模式串的散列值patHash
- 从文本第一个子串开始,依次向右移动,判断其散列值是否与patHash相等
3.2 利用蒙特卡洛法验证正确性:在之前我们讲过,当散列值相同时我们再逐个比较字符是否匹配,以此来确保我们得到的是一个匹配而非仅仅散列值相同的子串,但是我们可以不这么做。假设我们取一个1e20数量级的素数Q,那么一个随机键的散列值与模式串冲突的概率就会小于1e-20。这足以确保答案的正确性,如果仍不放心,我们可以再运行一次,这样就下降到了1e-40,同理你可以将概率降到你满意的数值(牺牲时间)。
3.3 代码示例:
/*
Rabin-Karp算法
-ValenShi
只能匹配数字,选R为10,若想匹配其他字符,需要调整。
*/
#include<cstdio>
#include<cstring>
typedef long long ll;
const int maxn = 1e5;
const int R = 10;
const int Q = 1e9+7;
char pat[maxn],txt[maxn];
int RM;
ll qpow(ll a,ll b,ll M){
ll res = 1;
while(b){
if(b&1) res = res*a%M;
a = a*a%M;
b >>= 1;
}
return res%M;
}
int charValue(char a[],int i){
return a[i]-'0'; //仅适用于数字
}
ll hash(char a[],int m){
ll res = 0;
for(int i = 0;i < m;i++)
res = res*R + charValue(a,i);
return res;
}
bool check(int x){
return true; //可在此对每个字符进行匹配
}
int solve(){
scanf("%s",txt);
scanf("%s",pat);
int m = strlen(pat);
int n = strlen(txt);
RM = qpow(R,m-1,Q);
ll patHash = hash(pat,m);
ll txtHash = hash(txt,m);
if(txtHash == patHash) return 0;
for(int i = 0;i < n-m;i++){
txtHash = (txtHash - RM*charValue(txt,i)%Q + Q)%Q;
txtHash = (txtHash*R + charValue(txt,i+m))%Q;
if(patHash == txtHash)
if(check(i-m+1)) return i+1;
}
return n; //未找到
}
int main(){
printf("%d",solve());
return 0;
}
3.4 使用注意:与KMP比起来,这个算法理解起来简单一些(如果熟悉散列表的话),也有趣一些,但是该算法需要注意R的选取。如果仅仅匹配数字字符串,那么R大于10皆可,若包括各种字符,我们则需要为每个字符设定一个R进制的值(可以使用ASCII码),这时要求R要大于字符总数(否则有冲突)。相比之下KMP算法等其他算法则没这方面的缺陷。
参考资料:
《算法导论》原书第三版 P580
《算法》第四版 [Robert Sedgewick] P505