问题
在文本text中查找模式pattern出现的所有位置(text长度为n,pattern长度为m,n,m都是正整数且n>m)。
解法
从位置i,j开始的匹配,每次成功的匹配都需要时间复杂度为O(m)的遍历,才能确定text[i…i+m−1]=pattern[0…m−1]=pattern是否成立。如果用hash(i,i+m−1)来表示字符串text[i…i+m−1]的哈希值,hash(pattern)表示字符串pattern的哈希值,则比较hash(i,i+m−1)=hash(pattern)是否成功的时间复杂度为O(1)。显然当hash(i,i+m−1)=hash(pattern)时必然有text[i…i+m−1]=pattern。反之若哈希值相同,再用简单匹配来确定字符串text[i…i+m−1]=pattern确实为真,这样即可找出所有成功匹配。
我们通过Rabin Fingerprint算法计算字符串的哈希值,ASCII码中每个字符对应的数字值范围在[0−127]之间,设每读入新的字符时旧的哈希值的扩大倍数为base=128(这个base是字符表大小的范围)。则有:
hash(0,i+1)=hash(0,i)⋅base+text[i+1] 实际我们想计算的是hash(text[i…i+m−1]),当i右移一位时,不仅要考虑右边界新加入的字符,还需要考虑左边界离开的字符。一个字符从最右边界一直移动到最左边界,其值乘以base共m−1次。因此哈希值要减去basem−1⋅text[i]。特别注意pattern长度为1时basem−1=1:
hash(i+1,i+m)=hash(i,i+m−1)⋅base−basem−1⋅text[i]+text[i+1] 由于数字太大时计算会溢出,用一个较大的素数来对结果取模prime=16777619,最终得到哈希函数:
hash(i+1,i+m])=(hash(i,i+m−1)⋅base−basem−1⋅text[i]+text[i+1]) Rabin Fingerprint算法可以连续的处理字符串,在时间复杂度为O(n)内求出所有字符串text[i…i+m−1]的哈希值(其中0≥i≥n−m+1)。Rabin-Karp算法的时间复杂度为O(n+z⋅m),其中z是模式pattern在文本中出现的次数。
Rabin-Karp
Rabin Fingerprint
源码
RabinKarp.h
RabinKarp.cpp
测试
RabinKarpTest.cpp