Pku1200 Crazy Search(Rabin Karp)
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和ts 可能是很大的數的情況)。可以用霍納規則(Horner’s rule) 在Θ(m) 的時間內計算p的值
p = P[m] + 10 (P[m - 1] + 10(P[m - 2] + · · · + 10(P[2] + 10P[1]) )).
Horner’s rule如果能在總共Θ(n - m + 1) 時間內計算出所有的ts 的值,那麼通過把p值與每個ts(有n-m+1個)進行比較,就能夠在Θ(m) + Θ(n - m + 1)= Θ(n) 時間內求出所有有效位移。(計算出1個ts 就跟p比較,處理結果。)
為了在Θ(n - m) 時間內計算出剩餘的值t1, t2, . . . , tn-m 可以在常數的時間內根據ts計算出ts+1,先看例子,假如m = 5,ts = 31415, 我們去掉高位數字T [s + 1] = 3,然後在加入一個低位數字T [s + 5 + 1](假設為2),得到:
ts+1 = 10(31415 - 10000 • 3) + 2 = 14152.
總結出公式: ——公式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和t0 以及公式1計算過程都可以對模q進行,所以可以在Θ(m)時間內計算出模q的p值,在Θ(n - m + 1)時間內計算出模q的所有ts值,通常選模q為一個素數,使得10q正好為一個計算機字長,單精度算術運算就可以執行所有必要的運算過程。 一般情況下,採用d進位制的字母表{0, 1, . . . , d - 1}, 所選的q要滿足d*q < 字長,調整公式1, 使其為:
其中的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。. 如果q足夠大,可以期望偽命中很少出現。 演算法RABIN-KARP-MATCHER(T, P, d, q)
1 n ← length[T]
2 m ← length[P]
3 h ← dm-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 qc++程式碼
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;
}