zoukankan      html  css  js  c++  java
  • 字符串匹配算法 -- 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
    ", 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 在最坏的情况下与朴素匹配一样,但是实际应用中往往比朴素算法快很多,应用还是很广的。

  • 相关阅读:
    〖C语言学习笔记 〗(一) HelloWorld
    〖C语言学习笔记 〗(二) 数据类型
    「学习笔记」进制转换
    Debian/Ubuntu 下网易云音乐无法由图标/列表 打开的解决方案
    Linux iptables常用命令的使用
    【学习笔记】Spring AOP注解使用总结
    【学习笔记】AspectJ笔记
    SpringMvc数据校验@Valid等注解的使用与工具类抽取
    Ubuntu 16.04开机自启Nginx简单脚本
    Spring Cloud (十五)Stream 入门、主要概念与自定义消息发送与接收
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3221620.html
Copyright © 2011-2022 走看看