字符串匹配算法 -- 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 在最壞的情況下與樸素匹配一樣,但是實際應用中往往比樸素算法快很多,應用還是很廣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章