给定字符串pattern
和串text
。求串pattern
在串text
中出现的位置。
暴力比较是逐个字符比较来确定两个串是否相等,若当前比较失败
则回到开始字符对应字符的后一个字符重复过程。
哈希就是一个大范围到小范围的映射
字符串哈希则是通过比较两个串的哈希值相等来判断两个字符串是
否相等,如果每次都要像暴力字符匹配那样重新计算哈希值的话,
那么复杂都就太高了。所以这里用到了一种技巧:滚动哈希。
由上面的图可以知道,要让字符串哈希求值快,则需要
Hash(0, p.size)
到Hash(1, p.size + 1)
的转换快。
所以我们这里直接自然想到了,进制的表示。
所以我们很自然的定义哈希函数
H ( s t r ) = a 0 s t r [ 0 ] + a 1 s t r [ 1 ] . . . + a s t r . s i z e ( ) s t r [ s t r . s i z e ( ) − 1 ] H(str) = a^0str[0] + a^{1}str[1]...+a^{str.size()}str[str.size() - 1] H(str)=a0str[0]+a1str[1]...+astr.size()str[str.size()−1]
则
H ( 0 , p a t . s i z e ( ) − 1 ) = H ( s t r ) H ( 1 , p a t . s i z e ( ) − 1 ) = H ( s t r ) − s t r [ 0 ] H(0, pat.size() - 1) = H(str) H(1, pat.size() - 1) = H(str) - str[0] H(0,pat.size()−1)=H(str)H(1,pat.size()−1)=H(str)−str[0]
推导式
H ( 1 , s t r . s i z e ( ) ) = ( H ( s t r ) − s t r [ 0 ] ) / b a s e + a p a t . s i z e ( ) − 1 ∗ s t r [ s t r . s i z e ( ) ] H(1, str.size()) = (H(str) - str[0])/base + a^{pat.size() - 1}*str[ {str.size()}] H(1,str.size())=(H(str)−str[0])/base+apat.size()−1∗str[str.size()]
如果直接这样运算的话,H(str)
的值会随着字符串长度的增加而呈指数级的增长。我
们希望值落在一个区间,所以通常会模上一个数使得哈希值在给定区间。
a % p = c < = > a = k p + c ( 0 < c < p ) a \% p = c <=> a = kp + c ( 0 < c < p) a%p=c<=>a=kp+c(0<c<p)
因为我们无法从
( ∑ i = 1 p . s i z e − 1 b a s e i t x t [ i ] ) % M O D ({\sum_{i=1}\limits^{p.size - 1}} {{base}^{i}txt[i]})\% MOD (i=1∑p.size−1baseitxt[i])%MOD
推出
( ∑ i = 1 p . s i z e − 1 b a s e i − 1 t x t [ i ] ) % M O D ({\sum_{i = 1}\limits^{p.size - 1}{base^{i - 1}txt[i]}}) \% MOD (i=1∑p.size−1basei−1txt[i])%MOD
因为
a ∗ b % M O D = ( a % M O D ) ∗ ( b % M O D ) % M O D a * b \% MOD = (a \% MOD) * (b \% MOD) \% MOD a∗b%MOD=(a%MOD)∗(b%MOD)%MOD
成立,而
a / b % M O D ≠ ( a % M O D ) / ( b % M O D ) % M O D a/b \% MOD \neq (a\%MOD)/(b\%MOD)\%MOD a/b%MOD=(a%MOD)/(b%MOD)%MOD
所以原来的哈希函数不行,而我们反着来的时候就可以了。
H ( 0 , p . s i z e − 1 ) = ∑ i = 0 p . s i z e − 1 b a s e p . s i z e − 1 − i ∗ s t r [ i ] H(0,p.size - 1) = \sum_{i = 0}\limits^{ p.size - 1} base^{p.size - 1 - i} * str[i] H(0,p.size−1)=i=0∑p.size−1basep.size−1−i∗str[i]
此时的H(0, p.size - 1)
到H(1, p.size)
推导式为
H ( 1 , p . s i z e ) = { ( H ( 0 , p . s i z e − 1 ) − ( s t r [ 0 ] b a s e p . s i z e − 1 % M O D ) + M O D ) % M O D ∗ b a s e + s t r [ p . s i z e ] } % M O D H(1, p.size) = \{(H(0, p.size - 1) - (str[0]base^{p.size - 1}\%MOD) + MOD)\%MOD*base + str[p.size]\} \%MOD H(1,p.size)={(H(0,p.size−1)−(str[0]basep.size−1%MOD)+MOD)%MOD∗base+str[p.size]}%MOD
所以我们应该先求出 b a s e p . s i z e − 1 % M O D base^{p.size - 1} \% MOD basep.size−1%MOD
求模运算时要特别注意是否有相减为负数的情况。
#include
#include
#include
#include
int robinKarp(const char *pat, const char *txt, uint8_t base)
{
if (!pat || !txt || !base)
return -1;
int pLen = (int) strlen(pat);
int tLen = (int) strlen(txt);
int MOD = 251;
if (pLen > tLen)
return -1;
int patHashVal = 0;
int mem_base = 1;
int cTxtHashVal = 0;
for ( int i = 0; i < pLen; ++i) {
cTxtHashVal += mem_base * txt[ pLen - 1 - i ] ;
patHashVal += mem_base * pat[ pLen - 1 - i ];
cTxtHashVal %= MOD;
patHashVal %= MOD;
if ( i + 1 != pLen)
mem_base = (mem_base * base) % MOD;
}
if (cTxtHashVal == patHashVal)
return 0;
for ( int i = pLen;i < tLen; ++i) {
cTxtHashVal = (cTxtHashVal - (mem_base * txt[i - pLen])%MOD + MOD)%MOD;
cTxtHashVal = (cTxtHashVal * base + txt[i]) % MOD;
if (cTxtHashVal == patHashVal) {
//printf("%d\t", i - pLen + 1);
return i - pLen + 1;
}
}
return -1;
}
int main( int argc, char **argv)
{
std::string txt("abeaabcabc");
std::string pat("abc");
int ret = robinKarp(pat.c_str(), txt.c_str(), 255);
if ( -1 != ret ) {
std::cout << "match pos: " << ret << std::endl;
}
return 0;
}
brilliant
geekforgeeks