Karp Rabin 算法是利用hash函数的特性进行字符串匹配的。
KR算法对模式串和循环中每一次要匹配的子串按一定的hash函数求值,如果hash值相同,才进一步比较这两个串是否真正相等
本文使用的hash函数如下:
hash(str[0..m-1])=(str[0]*2^(m-1)+str[1]*2^(m-2)+……+str[m-1]*2^0)mod q
q是一个较大的数,而且最好是素数,而且是大于m的素数。
计算时,*2的运算就使用移位代替了
在下面的实现代码中,q取整形最大值,也就是说,可以不用进行模运算了,这种偷工减料的做法只能在模式串很短的情形下才可以用,要不然会溢出的。粗略算了一下,如果模式串是ascii 英文字符,那么模式串长度不超过25的情况下,在32 位机上是没问题的
举例说一下KR算法吧:
在我写的这个总结中,各个算法使用的例子都一样,随便找的;
模式串 pattern="pappar"
文本串 text="pappappapparrassanuaragh";
pattern 长度记 pattern_len
预备阶段就是计算pattern的hash,长度为6,那么hash_pattern='p'*2^5+'a'*2^4+'p'*2^3+'p'*2^2+'a'*2^1+'r'*2^0
当然,这里使用的是字符的ascii值
也计算text前六个字符的hash,我们记第一个为hash_text[0]
然后就开始向前移动了,在移动时,要重新计算当前与模式串对应的串的hash值,这个工作叫rehash
初始化 i=0
如果 hash_pattern与hash_text[i]相等,返回 i
如果不等 计算新的hash值,就是text[i..i+patten_len]的hash,
当然这里不会像第一次那样全部计算,方法是
上一次计算的值减去上一次匹配时串的第一个字符乘以 2^pattern_len ,然后乘以2,再加上新加入比较的字符值
根据公式可以很清晰看出来。
就是减去计算中的第一项,把剩下的乘以2,然后在末尾加入新增的字符值
看代码吧,很简答的
[cpp] view plaincopy
//Karp-Rabin algorithm,a simple edition
int karp_rabin_search(const char* text,const int text_len,const char* pattern,const int pattern_len)
{
int hash_text=0;
int hash_pattern=0;
int i;
//rehash constant:2^(pattern_len-1)
int hash_const=1;
/*for (i=1;i<pattern_len;i++){
hash_const<<=1;
}*/
hash_const<<=pattern_len-1;
//preprocessing
//hashing
for (i=0;i<pattern_len;++i){
hash_pattern=(hash_pattern<<1)+pattern[i];
hash_text=(hash_text<<1)+text[i];
}
//searching
for (i=0;i<=text_len-pattern_len;++i){
if (hash_pattern==hash_text&&memcmp(text+i,pattern,pattern_len)==0){
return i;
}else{
//rehash
hash_text=((hash_text-text[i]*hash_const)<<1)+text[i+pattern_len];
}
}
return -1;
}
hash函数的好坏会直接影响算法的效率,一般应遵循这样的规则:
1 要容易计算,本文中用的就不错,移位的速度大家是知道的
而且在rehash,就是重新计算hash值时,hash的构造要能避免重新计算整个串的hash,而应该像本例中用到的那样,可以动态地很容易地更新
2 字符串hash值要尽量分布均匀,减少冲突,这是hash函数在任何场合的要求。做到这一点,就能减少匹配中字符的一个个比较,提高性能。如果能够保证每个串的hash值不同,就不用再比较字符了,可以省掉代码中的memcmp运算
Monte Carlo改进的 RK算法就是只比较hash值,虽然那个改进的算法不能保证正确的结果,但以低于2.53/pattern_len的错误率,而很实用