Rabin-Karp算法

Rabin-Karp算法在字符串匹配中其实也不算是很常用,但它的实用性还是不错的,除非你的运气特别差,最坏情况下可能会需要O((n-m)*m)的运行时间(关于n,m的意义请看上篇)。平均情况下,还是比较好的。

朴素的字符串匹配算法为什么慢? 因为它太健忘了,前一次匹配的信息其实可以有部分可以应用到后一次匹配中的,而朴素的字符串匹配算法只是简单的把这个信息扔掉,从头再来,因此,浪费了时间。好好的利用这些信息,自然可以提高运行速度。

这个算法不是那么容易说清楚,我举一个例子说下(看算法导论看到的例子)。

我们用E来表示字母表的字母个数,这个例子字母表如下:{0,1,2,3,4,5,6,7,8,9},那么E就是10,如果采用小写英文字母来做字母表,那么E就是26,类此。

由于完成两个字符串的比较需要对其中包含的字符进行检验,所需的时间较长,而数值比较则一次就可以完成,那么我们首先把模式(匹配的字串)转化成数值(转化成数值的好处不仅仅在此)。在这个例子里我们可以把字符0~9映射到数字0~9。比如,”423″,我们可以转化成3+E*(2+E*4)),这样一个数值,如果这个值太大了,我们可以选一个较大的质数对其取模,模后的值作为串的值。

这边处理好了,那么接下来转换被匹配的字符串,取前m个字符,如上述操作对其取值,然后对该值进行比较即可。

若不匹配,则继续向下寻找,这时候该如何做呢?比如模式是”423″,而父串是”324232″;第一步比较423跟324的值,不相等,下一步应该比较423跟242了,那么我们这步如何利用前一步的信息呢?首先我们把324前去300,然后在乘以E(这里是10),在加上2不就成了242了么?用个式子表示就是新的值a(i+1)=(E(a(i)-S[i])*h-S[S+M])) MOD p,p是我们选取的大质数,S[i]表示父串的第i个字符,而a(i)表示当前值,本例中就是324,h表示当前值最高位的权值,比如,324,则h=100,就是3这个位的权值,形式化的表示就是h=(E^m-1)MOD p。当然拉,由于采用了取模操作,当两者相等时,未必是真正的相等,我们需要进行细致的检查(进行一次朴素的字符串匹配操作)。若不相等,则直接可以排除掉。继续下一步。

伪代码,参加算法导论:

n = len(T);

m = len(p);

h = d^(m-1)mod q; //表示进位

p = 0;

t0 = 0;

for i   1 to m

   p = (d*p+P[I]) mod q;

   t0 = (d*t0+T[I])mod q;

for i 0 to n-m   //从串S里面开始逐个搜索

     if p==ti

     else ts+1 = d(ts-T[S+1]h) + T[S+M+1]mod q


问题描述:

      Rabin-Karp的预处理时间是O(m),匹配时间O( ( n - m + 1 ) m )既然与朴素算法的匹配时间一样,而且还多了一些预处理时间,那为什么我们还要学习这个算法呢?虽然Rain-Karp在最坏的情况下与朴素匹配一样,但是实际应用中往往比朴素算法快很多。而且该算法的期望匹配时间是O(n)【参照《算法导论》】,但是Rabin-Karp算法需要进行数值运算,速度必然不会比KMP算法快,那我们有了KMP算法以后为什么还要学习Rabin-Karp算法呢?个人认为学习的是一种思想,一种解题的思路,当我们见识的越多,眼界也就也开阔,面对实际问题的时候,就能找到更加合适的算法。比如二维模式匹配,Rabin-Karp就是一种好的选择。

      而且Rabin-Karp算法非常有趣,将字符当作数字来处理,基本思路:如果Tm是一个长度为 |P| 的T的子串,且转换为数值后模上一个数(一般为素数)与模式字符串P转换成数值后模上同一个数的值相同,则Tm可能是一个合法的匹配。


代码示例:

  1. <span style="font-size:18px;">#include "iostream"  
  2. #include "string"  
  3. #include "cmath"  
  4. using namespace std;  
  5.   
  6. // get the value of the character in the set   
  7. int getV(char p, string set)  
  8. {  
  9.     for(int i=0; i<set.length(); i++)  
  10.     {  
  11.         if (p==set[i])  
  12.             return i;  
  13.     }  
  14.     return -1;  
  15. }  
  16. // d is the size of the character set  
  17. int RK(string T, string P,string set)  
  18. {  
  19.     int d = int(set.length());  
  20.     int n = T.length();  
  21.     int m = P.length();  
  22.     int h = pow(double(d), m-1);  
  23.     int p=0;  
  24.     int t = 0;  
  25.     for(int i=0; i<m; i++)  
  26.     {  
  27.         p = d*p + getV(P[i],set);  
  28.         t = d*t + getV(T[i], set);  
  29.     }  
  30.     for (int s=0; s<=n-m; s++)  
  31.     {  
  32.         cout<<"p,t is "<<p<<","<<t<<endl;  
  33.         if (p==t)  
  34.             return s;  
  35.         if (s<n-m)  
  36.             t = getV(T[s+m],set)+d*(t-h*getV(T[s],set));  
  37.     }  
  38.     return -1;  
  39. }  
  40. int main()  
  41. {  
  42.     // set is the character set  
  43.     string set= "0123456789";  
  44.     // pattern P  
  45.     string P = "2365";  
  46.     // T is the string to match  
  47.     string T = "258569236589780";  
  48.     int i = RK(T, P, set);  
  49.     cout<<"the postition is:"<<i<<endl;  
  50.     return 0;  
  51. }  
  52. </span>  
参考资料:http://www.iteye.com/topic/287105


    以上算法很简单,但是当模式字符串P的长度达到7以后就要出错了,即使将t,p定义为long unsigned int型也解决不了大问题,也就是说上面代码没什么用。


    该算法的难点就在于p和t的值可能很大,导致不能方便的对其进行处理。对这个问题有一个简单的补救办法,用一个合适的数q来计算p和t的模。每个字符其实十一个十进制的整数,所以p,t以及递归式都可以对模q进行,所以可以在O(m)的时间里计算出模q的p值,在O(n - m + 1)时间内计算出模q的所有t值。参见《算法导论》或http://net.pku.edu.cn/~course/cs101/2007/resource/Intro2Algorithm/book6/chap34.htm

递推式是如下这个式子: 

ts+1 = (d ( ts -T[s + 1]h) + T[s + m + 1 ] ) mod q

例如,如果d = 10 (十进制)m= 5, ts = 31415,我们希望去掉最高位数字T[s + 1] = 3,再加入一个低位数字(假定 T[s+5+1] = 2)就得到:

ts+1 = 10(31415 - 1000*3) +2 = 14152


代码示例:

  1. <span style="font-size:18px;">/* 
  2. *Copyright(c) Computer Science Department of XiaMen University  
  3. * 
  4. *Authored by laimingxing on: 2012年 03月 04日 星期日 18:18:28 CST 
  5. * 
  6. * @desc: 
  7. * 
  8. * @history 
  9. */  
  10. #include <stdio.h>  
  11. #include <math.h>  
  12. #include <assert.h>  
  13. #include <string.h>  
  14. #include <stdlib.h>  
  15. #define d 256// number of characters in the alphabet  
  16. #define PRIME 127 //A prime number  
  17.   
  18.   
  19. void RABIN_KARP_MATCHER( char *T, char *P, int q)  
  20. {  
  21.     assert( T && P && q > 0 );  
  22.     int M = strlen( P );  
  23.     int N = strlen( T );  
  24.     int i, j;  
  25.     int p = 0;//hash value for pattern  
  26.     int t = 0;//hash value for txt  
  27.     int h = 1;  
  28.       
  29.     //the value of h would be "pow( d, M - 1 ) % q "      
  30.     for( i = 0; i < M - 1; i++)  
  31.         h = ( h * d ) % q;  
  32.   
  33.     for( i = 0; i < M; i++ )  
  34.     {  
  35.         p = ( d * p + P[i] ) % q;  
  36.         t = ( d * t + T[i] ) % q;  
  37.     }  
  38.       
  39.     //Slide the pattern over text one by one  
  40.     for( i = 0; i <= N - M; i++)  
  41.     {  
  42.         if( p == t)  
  43.         {  
  44.             for( j = 0; j < M; j++)  
  45.                 if(T[i+j] != P[j])  
  46.                     break;  
  47.             if( j == M )  
  48.                 printf("Pattern occurs with shifts: %d\n", i);  
  49.         }  
  50.         //Caluate hash value for next window of test:Remove leading digit,  
  51.         //add trailling digit  
  52.         if( i < N - M )  
  53.         {  
  54.             t = ( d * ( t - T[i] * h ) + T[i + M] ) % q;  
  55.             if( t < 0 )  
  56.                 t += q;//按照书上的伪代码会出现t为负的情况,则之后的计算就失败了。  
  57.         }  
  58.     }  
  59. }     
  60.   
  61. int main(int argc, char* argv[])  
  62. {  
  63.     char txt[] = "GEEKS FOR GEEKS";  
  64.     char pat[] = "GEEK";  
  65.     RABIN_KARP_MATCHER( txt, pat, 127 );  
  66.       
  67.     return 0;  
  68. }</span>  


  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值