字符串匹配算法 -- Rabin-Karp 算法

字符串匹配算法 -- Rabin-Karp 算法
参考资料
1 算法导论

Rabin-karp 算法简介

在实际应用中,Rabin-Karp 算法对字符串匹配问题能较好的运行。Rabin-Karp 算法需要对字符串和模式进行预处理,其预处理时间为 O ( m ) ,在最坏情况下的运行时间为 O ( ( n-m+1 ) m ) ,但基于某种假设(不知道是何种假设),它的平均情况下的运行时间还是比较好的。
为了便于说明,假设  ∑ = { 0,1,2.....9 },这样每个字符都是一个十进制数字。(对于更一般的情况,可以假设每个字符都是基数为 d 的表示法中的一个数字,d = | ∑ | 。)可以用一个长度为 k 的十进制数来表示由 k 个连续字符组成的字符串。因此,字符串31415 就对应于十进制数 31415 。

已知一个模式 P[ 1.. m ],设 p 表示该模式所对应的十进制数的值(如模式 P = "31415" ,数值p = 31415)。对于给定的文本 T [ 1.. n ],用 ts 来表示其长度为 m 的子字符串 T [ s+1.. s+m ] (s = 0,1,.. n-m)相对应的十进制数的值。ts = p 当且仅当 T [ s+1.. s+m ] = P[ 1.. m ] ,因此 s 是有效位移当且仅当 ts = p 。

预处理 -- p 和 t0

于是应用霍纳法则(Horner's Rule)在 O ( m ) 的时间内计算 p 的值:
         p = P[ m ] + 10( P[ m-1 ] + 10 ( P[ m-2 ] + .. + 10( P[ 2] + P[ 1 ]) ... ))
类似的,也可以在 O ( m ) 时间内根据 T[ 1.. m ] 计算出 t0 的值。
为了在 O ( n - m ) 的时间内计算出剩余的值 t1,t2,...,t ( n - m ),可以在常数时间内根据 ts 计算出 t ( s+1 )。因为
        t ( s + 1) = 10 ( ts - 10^(m-1) T [ s+1 ] ) + T [ s + m +1]             ( 1 )
事实上,就是去掉最高位,然后左移了一位,在加上 T [ s + m +1] ,就得到了 t ( s + 1) 。
预处理的时间为 O ( m )

字符串匹配

当进行完预处理之后,就可以执行字符串匹配了。我们只需要将 ti ( i = 0 , 1 ,  ...  n-m ) 与 p 进行比较,相等则为合法匹配,否则为非法匹配。整个匹配过程的时间为 O ( n -m + 1 )

然而,上述问题对于模式 p 的长度较小时,比较方便。当 p 和 ts 的值很大时,p 的结果会太大,以至于不能很好的处理这类问题 。所以才有了下面的改进版本。


补救方法

对一个合适的模 q 来计算 p 和 ts 的模。每个字符是一个十进制数,因为 p ,t0 以及递归式 1 计算过程都可以对模 q 进行,所以可以在 O ( m ) 时间内计算出模 q 的 p 值。在时间 O( n-m+1 ) 计算出模 q 的所有 ts 值。通常选模 q 为一个素数,使得 10q 正好为一个计算机字长。

在一般情况下,采用 d 进制的字母表 { 0 ,1,... ,d - 1 } 时,所选取的 q 要满足使 dq 的值在一个计算机字长内,并调整递归式 ( 1 ) 以使对模 q 进行运算,使其成为
            t ( s + 1) = ( d ( ts -  T [ s+1 ] h ) + T [ s + m +1]  ) mod q
其中 h  ≡ d ^ ( m-1 ) (mod q) 。

加入模 q 后,我们已经不能通过 ts ≡ p (mod q ) 并不能说明 ts = p 。当 ts ≡ p (mod q ) 不成立时,则肯定 ts != p 。因此,当 ts ≡ p (mod q ) 时我们还需要进一步进行测试,看看 ts 是否等于  p ,因为 ts 可能是匹配的也有可能是伪匹配的。

这个算法就是有点使用hash的思想了。把模式字符串进行一个预处理,并mod,主字符串进行逐个进行简单的hash映射,然后mod比较。

伪代码如下
RABIN-KARP-MATCHER( T,P,d,q)
1  n ← length[ T ]
2  m ← length[ P]
3  h  ← d^(m-1) mod q
4  p  ← 0
5  t0 ← 0
6  for i ← 1 to  m         Preprocessing(预处理)
7           do p ← (dp + P[i]) mod q
8                t0 ← (dt0 + T[i]) mod q
9      for s ← 0 to s-m     Matching( 匹配 )
10          do if  p = t
11                then  if P[1..m] = T[s+1..s+m]        对p 和 T 中的每个字符进行判断
12                     then   print "匹配"
13              if s < n - m
14                then t(s+1) ← (d (ts - T[s+1] h) + T[s+m+1]) mod q

代码实现 
*Copyright(c) Computer Science Department of XiaMen University  
* 
*Authored by laimingxing on: 2012年 03月 04日 星期日 18:18:28 CST 
* 
* @desc: 
* 
* @history 
*/  
// d = 256 ; q = 127

void RABIN_KARP_MATCHER( char *T, char *P, int q)  
{  
    assert( T && P && q > 0 );  
    int M = strlen( P );  
    int N = strlen( T );  
    int i, j;  
    int p = 0;//hash value for pattern  
    int t = 0;//hash value for txt  
    int h = 1;  
      
    //the value of h would be "pow( d, M - 1 ) % q "      
    for( i = 0; i < M - 1; i++)  
        h = ( h * d ) % q;  
  
    for( i = 0; i < M; i++ )  
    {  
        p = ( d * p + P[i] ) % q;  
        t = ( d * t + T[i] ) % q;  
    }  
      
    //Slide the pattern over text one by one  
    for( i = 0; i <= N - M; i++)  
    {  
        if( p == t)  
        {  
            for( j = 0; j < M; j++)  
                if(T[i+j] != P[j])  
                    break;  
            if( j == M )  
                printf("Pattern occurs with shifts: %d\n", i);  
        }  
        //Caluate hash value for next window of test:Remove leading digit,  
        //add trailling digit  
        if( i < N - M )  
        {  
            t = ( d * ( t - T[i] * h ) + T[i + M] ) % q;  
            if( t < 0 )  
                t += q;//按照书上的伪代码会出现t为负的情况,则之后的计算就失败了。  
        }  
    }  
}     

Rabin-Karp-Matcher 的预处理时间为 O ( m ) ,其匹配时间在最坏情况下为 O ( ( n- m + 1) m) , 虽然 Rabin-Karp-Matcher 在最坏的情况下与朴素匹配一样,但是实际应用中往往比朴素算法快很多,应用还是很广的。

 

posted @ 2013-07-28 20:50  坚固66  阅读(324)  评论(0编辑  收藏  举报