1. 程式人生 > >Pku1200 Crazy Search(Rabin Karp)

Pku1200 Crazy Search(Rabin Karp)

NAIVE-STRING-MATCHER(T, P) 1 n ← length[T] 2 m ← length[P] 3 for s ← 0 to n - m
4     do if P[1 ‥ m] = T[s + 1 ‥ s + m]         // 隱含著一個迴圈
5           then print "Pattern occurs with shift" s 時間複雜度為O((n - m + 1)m), 如果m = �n/2�. 那麼時間複雜度為Θ(n2), 這個演算法效率不高,原因在於對於s的一個值,獲得的關於文字的資訊在考慮s的其他值時完全被忽略了。 例如,如果 P=aaab,設s=0 是有效的,那麼 s=1, 2, 3 就不可能是有效位移,因為T[4]=b. 2.) Rabin-Karp字串匹配演算法, 實際應用中,Rabin和Karp建議的字串匹配演算法能較好地執行,還可以歸納出有關問題地其他演算法,如二維模式匹配。 假定字符集Σ ={0, 1, 2, ……, 9}, 每一個字元對應一個十進位制數字 (一般情況, 假定每個字元是基數為d的表示法中的一個數字, d=|Σ|。)可以用一個長度為k的十進位制數字來表示由k個連續字元組成的字串.

因此,字串"31415" 對應於十進位制數31415

已知模式P[1..m],設p表示其相應十進位制數地值,類似地, 對於給定的文字T[1..n]. 用

ts 表示長度為m的子字串 T[s + 1 ‥ s + m]( s = 0, 1, . . . , n – m), ts = p 當且僅當 [s + 1 ‥ s + m] = P[1 ‥m]; 因此s是有效位移當且僅當 ts = p. (暫不考慮p和t可能是很大的數的情況)。

可以用霍納規則(Horner’s rule) 在Θ(m) 的時間內計算p的值

p = P[m] + 10 (P[m - 1] + 10(P[m - 2] + · · · + 10(P[2] + 10P[1]) )).

Horner’s rule

http://p.blog.csdn.net/images/p_blog_csdn_net/touzani/303255/o_image002.jpg

類似地,可以在Θ(m)時間內,根據T[1..m]計算出t的值。

如果能在總共Θ(n - m + 1) 時間內計算出所有的t的值,那麼通過把p值與每個ts(有n-m+1個)進行比較,就能夠在Θ(m) + Θ(n - m + 1)= Θ(n) 時間內求出所有有效位移。(計算出1個t就跟p比較,處理結果。)

為了在Θ(n - m) 時間內計算出剩餘的值t1t2, . . . , tn-可以在常數的時間內根據ts計算出ts+1,先看例子,假如m = 5,ts = 31415, 我們去掉高位數字T [s + 1] = 3,然後在加入一個低位數字T [s + 5 + 1](假設為2),得到:

ts+1 = 10(31415 - 10000 • 3) + 2 = 14152.

總結出公式:http://p.blog.csdn.net/images/p_blog_csdn_net/touzani/303255/o_image004.jpg      ——公式1

如果預先計算出10m-1(通過數論中的技術可以在O(lg m)完成, 在這裡只需簡單地在O(m)時間內計算出就可以)。那麼就可以在常數時間計算出ts+1

因此,可以在Θ(m)時間內計算出p和t0然後在Θ(n - m + 1)時間內計算出t1, . . . , tn-m 並完成匹配。

現在來解決唯一的問題,就是計算中p和ts的值可能太大,超出計算機字長,不能方便地進行處理。如果p包含m個字元,那麼, 關於在p上地每次算術運算需要“常數”時間這一假設就不合理了,幸運的是,對這一問題存在一個簡單的補救方法,對一個合適的模q來計算p和ts的模,每個字元是一個十進位制數,因為p和t以及公式1計算過程都可以對模q進行,所以可以在Θ(m)時間內計算出模q的p值,在Θ(n - m + 1)時間內計算出模q的所有ts值,通常選模q為一個素數,使得10q正好為一個計算機字長,單精度算術運算就可以執行所有必要的運算過程。 一般情況下,採用d進位制的字母表{0, 1, . . . , d - 1}, 所選的q要滿足d*q < 字長,調整公式1, 使其為:http://p.blog.csdn.net/images/p_blog_csdn_net/touzani/303255/o_image006.jpg

其中的h = m-1 (mod q)

但是加入模q後,由ts ≡ p (mod q)不能說明 ts = p. 但ts � p (mod q), 可以說明 ts ≠ p,

因此當ts ≡ p (mod q)時, 再用樸素的字串匹配演算法驗證ts = p。. 如果q足夠大,可以期望偽命中很少出現。 演算法
RABIN-KARP-MATCHER(T, P, d, q)
 1 nlength[T]
 2 mlength[P]
 3 hdm-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 n - m        Matching.
10     do if p = ts
11           then if P[1 ‥ m] = T [s + 1 ‥ s + m]
12                   then print "Pattern occurs with shift" s
13        if s < n - m
14           then ts+1 ← (d(ts - T[s + 1]h) + T[s + m + 1]) mod q
c++程式碼
void RABIN_KARP_MATCHER(string T, string P, int d, int q)  
/* 
搜尋P在T中出現的位置
引數d :字母表的進位制,亦即是字母表的元素個數
引數q : 一個較大的素數, 只需d*q < 字長
*/
{
    
    int n= T.length();
    int m= P.length();
    if( n < m)
        return ;
    int i, h=1;
    for(i=1; i<=m-1; i++)   // caculate h 
        h = h*d%q;

    int p=0, t=0;
    for(i=0; i<m; i++)      //  預處理,計算p, t0 
    {
        p = (( d*p + P[i]) % q); 
        t = (( d*t + T[i]) % q);
    }

    int s;
    for(s=0; s < n-m+1; s++)   //    匹配
    {
        if( p == t )
        {
            for(i=0; i<m; i++)        // 進一步驗證
                if(P[i]!=T[s+i])
                    break;

            if(i==m)
                cout<<"Pattern ocurs with shift "<<s<<endl;
        }
        if( s < n-m )
            t= (  d* (t - T[s]*h%q + q) + T[s+m])  % q;  // 計算ts+1
    }
    cout<<"string matching ends"<<endl;
    return  ;
}

關於C++ % 運算子例子,見

RABIN_KARP_MATCHER預處理時間為Θ(m) 匹配時間最壞情況下為Θ((n - m + 1)m),

因為Rabin_Karp演算法跟樸素的字串匹配演算法一樣,對每個有效位移進行顯示驗證,如果P = am and T = an, 則驗證所需時間為Θ((n - m + 1)m), 因為n - m + 1個可能的位移中每一個都是有效位移。

實際應用中,有效位移數很少(常數c個),因此期望的匹配時間為O((n - m + 1) + cm) = O(n+m), 選取的素數q比p的長度m大得多。

通常處理ASCII碼字元, d=128, 素數q可選6999997。

附上AC程式碼:

#include <bits/stdc++.h>
using namespace std;
 
typedef long long ll;
const int INF=0x3f3f3f3f;
const int H=16000000;
const int maxn=4005;
int n,nc;
char s[H];
int flag[maxn];
int Hash[H];
 
int main(){
    scanf("%d%d",&n,&nc);
        scanf("%s",s);
        memset(flag,0,sizeof(flag));
        memset(Hash,0,sizeof(Hash));
        int len=strlen(s),num=1;
        for(int i=0;i<len;i++){
            if(!flag[s[i]]){
                flag[s[i]]=num++;
            }
        }
        int count=len-n+1;
        for(int i=0;i<len-n+1;i++){
            int sum=0;
            for(int j=i;j<i+n;j++){
                sum=sum*nc+flag[s[j]];
            }
            sum=sum%H;
            if(Hash[sum]) count--;
            else Hash[sum]=1;
        }
        printf("%d\n",count);
    return 0;
}