对于任意一个串,串中字符集的大小为d,则该串中的任意一个字符都可以用一个d+1进制的整数来表示。需要注意的是,这里是d+1进制,而不是d进制,是因为不能用0来表示任意一个字符,否则如果该字符组成串的一个前缀,无论前缀的长度多少,都不会影响串所对应的整数取值。
Rabin-Karp算法,它是字符串快速查找的一种算法,解决思路是把一个字符串,看作是字符集长度进制的树,如果是ASCII,这个进制就是128,如果是只考虑英文小写字母,那这个进制就是26,通过数值的比较得出字符串的比较结果。
如果我们要在 ASCII 字符集范围内查找“搜索词”,由于 ASCII 字符集是0~127一共 128 个字符,那么 d 就等于 128,比如我们要在字符串 “abcdefg” 中查找 “cde”,那么我们授信计算模式字符串 “cde”的hash指纹:
n=len(“cde”)
d=128
Hash(cde)= c_ASCII*d^(n-1)+ d_ASCII*d^(n-2)+ e_ASCII*d^(n-3)
=99*128^2+100*128^1+101*128^0
=1634917
模式字符串的hash值计算完之后开始计算源串的hash值,计算方法与上面相同:
Hash(abc)= a_ASCII*d^(n-1)+ b_ASCII*d^(n-2)+ c_ASCII*d^(n-3)
=97*128^2+98*128^1+99*128^0
=1601891
其中:
Hash(a)= a_ASCII*d^(n-1) =97*128^2= 1589248
Hash(b)= b_ASCII*d^(n-1) =98*128^1= 12544
Hash(c)= c_ASCII*d^(n-1) =99*128^0=99
显而易见的Hash(abc)不等于Hash(cde),源串的窗口向后滑动一位计算hash(bcd), hash(bcd)的计算如果还是按照上面的方法的话Rabin-Karp的时间复杂度和暴力解法一样也是O(m*n),hash(bcd) 可以看成hash(abc)中去掉Hash(a),然后加入Hash(d),如此一来就将计算指纹的时间复杂度由O(n)降低到O(1):
Hash(bcd)= (Hash(abc)- Hash(a))*d+ Hash(d),这样就可以继续比较 “cde” 和 “bcd” 是否匹配,以此类推。
由上面的描述可以推导出更一般的规律如下:
hash( txt[s+1 .. s+m] ) = ( d ( hash( txt[s .. s+m-1]) – txt[s]*h ) + txt[s + m] ) mod q
hash( txt[s .. s+m-1] ) : Hash value at shift s .
hash( txt[s+1 .. s+m] ) : Hash value at next shift (or shift s +1)
d : Number of characters in the alphabet
q : A prime number
h: d^(m-1)
加入模q后, H = H’mod q.对于指纹相同的串还要在进行一次比对校验,如果q足够大,碰撞的几率会很少出现。
Rabin-Karp算法的思想:
假设子串的长度为M,目标字符串的长度为N
计算子串的hash值
计算目标字符串中每个长度为M的子串的hash值(共需要计算N-M+1次)
比较hash值
如果hash值不同,字符串必然不匹配,如果hash值相同,还需要使用朴素算法再次判断
为了快速的计算出目标字符串中每一个子串的hash值,Rabin-Karp算法并不是对目标字符串的 每一个长度为M的子串都重新计算hash值,而是在前几个字串的基础之上, 计算下一个子串的 hash值,这就加快了hash之的计算速度,将算法中的内循环的世间复杂度从O(M)将到了O(1)。
代码示例:
d取字符集的个数
#define d 256
/* S -> Search Sequence
T -> Target Sequence
q -> A prime number
*/
void search(char *S, char *T, int q) {
int M = strlen(S);
int N = strlen(T);
int i, j;
int SHashValue = 0; // hash value for Search Sequence
int THashValue = 0; // hash value for Target Sequence
int h = 1; // h 的计算公式:pow(d, M-1) % q
//计算h
for (i = 0; i < M-1; i++)
h = (h * d) % q;
for (i = 0; i < M; i++) {
SHashValue = (d * SHashValue + S[i]) % q;
THashValue = (d * THashValue + T[i]) % q;
}
for (i = 0; i <= N - M; i++) {
if ( SHashValue == THashValue ) {
for (j = 0; j < M; j++) {
if (txt[i+j] != pat[j])
break;
}
if (j == M) { // if SHashValue == THashValue and S[0...M-1] = T[i, i+1, ...i+M-1]
cout << "Pattern found at index " << i << endl;
}
}
if ( i < N-M )
{
THashValue = (d * (THashValue - T[i] * h) + T[i+M]) % q;
if(THashValue < 0)
THashValue = (THashValue + q);
}
}
}