KMP
核心
玄學 數組
數組的含義即是某段字符串的最長公共前後綴。
具體來講,設 ,那麼 爲 的最大值。
那麼這個 有什麼用呢?見下圖。
匹配時,設當前匹配到 的第 位, 的第 位,即 與 已成功匹配。當 指針這一位發生失配,意味着 ,這時根據 數組的定義,由於 ,因此若將 指針指向 ,我們可以直接跳過對 的匹配。
複雜度證明
設 爲串 的長度, 爲串 的長度。
指針全程只增,這裏的複雜度爲 ;
指針全程只有兩種跳法: 或 。
對於 全程最多跳 次。
對於 :
在 保持不變的情況下, 跳至下界的極限次數一定不超過 (根據 的定義)。因此設 表示 這個位置發生失配跳至下界的上限次數, 爲跳完 次之後 的位置。而 每跳一次, 一定減小至少 , 隨之減小至少 ,從而最終跳的次數上界爲 。由 的定義我們知道, ,故最終 跳的次數一定不超過 。
又由於要單獨對串 單獨求一次 ,複雜度證明同上,爲 。
綜上,由於 全程迭代 次, 全程迭代不超過 次,故時間複雜度爲 。
擴展KMP
“擴展”
引入 數組, 表示 與 的最長公共前綴( 爲串 的長度, 爲串 的長度)。
考慮如何求 。
引入輔助工具
設當前需要計算 的值, 爲 最大時 的值 ,
就有 ,
於是 。
這時求 就有兩種情況:
1、
由於 ,
又由 的定義知 , 爲最大匹配長度,故 。
2、
此處求法與 求法幾乎一樣,可參考 的求值。
複雜度證明:對於情況1,單次複雜度爲 ;對於情況2,總複雜度與KMP算法中一致,爲 。
代碼實現
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int LENGTH=1000000;
char S[LENGTH+2],T[LENGTH+2];
namespace KMP{
int fail[LENGTH+2];
int cnt[LENGTH+2],ext[LENGTH+2];
void get_fail(char *t){
int len=strlen(t+1);
for(int i=2,j=0;i<=len;++i){
while(j&&t[i]!=t[j+1])j=fail[j];
if(t[i]==t[j+1])fail[i]=++j;
}
}
void KMP(char *s,char *t){
get_fail(t);
int s_len=strlen(s+1),t_len=strlen(t+1);
for(int i=1,j=0;i<=s_len;++i){
while(j&&s[i]!=t[j+1])j=fail[j];
if(s[i]==t[j+1]){
cnt[i]=++j;
if(j==t_len)j=fail[j];
}
}
}
void ex_KMP(char *s,char *t){
get_fail(t);
int s_len=strlen(s+1),t_len=strlen(t+1);
int p=1;
while(ext[1]<t_len&&s[ext[1]+1]==t[ext[1]+1])++ext[1];
for(int i=2;i<=s_len;++i){
if(i+fail[i-p+1]<p+ext[p])ext[i]=fail[i-p+1];
else{
int j=ext[p]+p-i;
if(j<0)j=0;
while(i+j<=s_len&&j<t_len&&s[i+j]==t[j+1])++j;
ext[i]=j;
p=i;
}
}
}
}
int main(){
scanf("%s%s",S+1,T+1);
KMP::KMP(S,T);
KMP::ex_KMP(S,T);
int s_len=strlen(S+1),t_len=strlen(T+1);
printf("Array of fail:\n\t");
for(int i=1;i<=t_len;++i)printf("%d ",KMP::fail[i]);
printf("\nMatching position:\n\t");
for(int i=1;i<=s_len;++i)if(KMP::cnt[i]==t_len)printf("%d ",i-t_len+1);
printf("\nArray of extend:\n\t");
for(int i=1;i<=s_len;++i)printf("%d ",KMP::ext[i]);
}