RabinKarp RabinKarp算法

问题

在文本texttext中查找模式patternpattern出现的所有位置(texttext长度为nnpatternpattern长度为mmn,mn, m都是正整数且n>mn \gt m)。

解法

从位置i,ji, j开始的匹配,每次成功的匹配都需要时间复杂度为O(m)O(m)的遍历,才能确定text[ii+m1]=pattern[0m1]=patterntext[i \dots i+m-1] = pattern[0 \dots m-1] = pattern是否成立。如果用hash(i,i+m1)hash(i, i+m-1)来表示字符串text[ii+m1]text[i \dots i+m-1]的哈希值,hash(pattern)hash(pattern)表示字符串patternpattern的哈希值,则比较hash(i,i+m1)=hash(pattern)hash(i, i+m-1) = hash(pattern)是否成功的时间复杂度为O(1)O(1)。显然当hash(i,i+m1)hash(pattern)hash(i, i+m-1) \ne hash(pattern)时必然有text[ii+m1]patterntext[i \dots i+m-1] \ne pattern。反之若哈希值相同,再用简单匹配来确定字符串text[ii+m1]=patterntext[i \dots i+m-1] = pattern确实为真,这样即可找出所有成功匹配。

我们通过Rabin Fingerprint算法计算字符串的哈希值,ASCII码中每个字符对应的数字值范围在[0127][0 - 127]之间,设每读入新的字符时旧的哈希值的扩大倍数为base=128base = 128(这个basebase是字符表大小的范围)。则有:

hash(0,i+1)=hash(0,i)base+text[i+1]hash(0, i+1) = hash(0, i) \cdot base + text[i+1]

实际我们想计算的是hash(text[ii+m1])hash(text[i \dots i+m-1]),当ii右移一位时,不仅要考虑右边界新加入的字符,还需要考虑左边界离开的字符。一个字符从最右边界一直移动到最左边界,其值乘以basebasem1m-1次。因此哈希值要减去basem1text[i]base^{m-1} \cdot text[i]。特别注意patternpattern长度为11basem1=1base^{m-1} = 1

hash(i+1,i+m)=hash(i,i+m1)basebasem1text[i]+text[i+1]hash(i+1, i+m) = hash(i, i+m-1) \cdot base - base^{m-1} \cdot text[i] + text[i+1]

由于数字太大时计算会溢出,用一个较大的素数来对结果取模prime=16777619prime = 16777619,最终得到哈希函数:

hash(i+1,i+m])=(hash(i,i+m1)basebasem1text[i]+text[i+1])hash(i+1, i+m]) = (hash(i, i+m-1) \cdot base - base^{m-1} \cdot text[i] + text[i+1]) % prime

Rabin Fingerprint算法可以连续的处理字符串,在时间复杂度为O(n)O(n)内求出所有字符串text[ii+m1]text[i \dots i+m-1]的哈希值(其中0inm+10 \ge i \ge n-m+1)。Rabin-Karp算法的时间复杂度为O(n+zm)O(n + z \cdot m),其中zz是模式patternpattern在文本中出现的次数。

Rabin-Karp

Rabin Fingerprint

源码

RabinKarp.h

RabinKarp.cpp

测试

RabinKarpTest.cpp

Last updated