Kmp算法是一种实用的快速字符串匹配算法。
1.什么是字符串匹配?
字符串匹配便是在字符串s中查找字符串t.
2.如何进行匹配?
我们定义字符串s为主串,长度为n;字符串t为模式串,长度为m,考虑如何在主串中查找模式串。
先来考虑暴力的方法,暴力for主串中的每个位置,对于每个位置再次for一次模式串中的每个位置,看看是否可以匹配,时间复杂度O(nm);
3.Kmp算法的本质
在暴力的算法中,我们对主串的每个位置都进行了一次字符串匹配,可是在每一次匹配时,我们每次都扫描了一次模式串,导致了时间的浪费。对于主串的每一个位置,如果可以在失配后快速找到一个可以匹配的位置,继续进行匹配,那就可以使匹配的速度加快。(理解不了?看下面的例子。)
主串:abaabaababa
模式串:ababa
在匹配时,先从1号位置进行匹配
a b a a b a a b a b a
| | | x
a b a b a
发现无法进行匹配了,无需一位位移动,直接找到可以匹配的位置进行匹配
a b a a b a a b a b a
| x
a b a b a
发现无法匹配,再进行移动
a b a a b a a b a b a
| | | x
a b a b a
再进行移动
a b a a b a a b a b a
| x
a b a b a
移动
a b a a b a a b a b a
| | | | |
a b a b a
发现可以匹配,得到答案。
我们发现,当在匹配时无法继续进行匹配时,可以通过移动模式串的位置使得移动到位置的最长后缀等于原位置的最长前缀,而Kmp的奥妙便在于处理这个移动的方法。
4.Kmp的实现方法
Kmp对处理移动时引入了一个next数组,next[i]表示与i位置有着最长后缀的最长前缀。这么说有些绕,还是看例子中的模式串ababa。next[1]=0, next[2]=0, next[3]=1, next[4]=2, next[5]=3。next数组还可以看作是位置i(不包括i)的前缀中前缀与后缀的最长公共部分。以next[4]为例,位置为4的前缀为abab,它的前缀与后缀的最长公共部分为ab,长度为2,所以next[4]便是2.
那么next数组应该怎么求呢,先来看看以下求next数组的方法:
1 void getnext(void) { 2 next[1]=0; 3 int k=0; 4 for (int i=2;i<=m;++i) { 5 while (k>0 && t[i]!=t[k+1]) k=next[k]; 6 if (t[i]==t[k+1]) k++; 7 next[i]=k; 8 } 9 }
next[i]表示的是不包括i位置的前缀中前缀与后缀的最长公共部分,而next[1]则不包括1位置,故为0. k记录的是前缀与后缀的最长公共部分(级next的值),之后对于next[i]的值,则可以通过递推得出。若当前的位置为i,若当前位置可以与前一个next值+1匹配,则当前的next值便为之前的值+1. 若当前的无法进行匹配,则k = next[k], 因为k位置的最长前缀中前缀与后缀的最长公共部分为next[k], 所以next[k]也为i位置的最长前缀与后缀的公共部分。有while进行寻找后,再判断当前的位置是否和k+1的位置相等,便可得出当前的next值。(关说难以理解,可以用数据手动模拟,渐渐就可以理解了。)
说完了求next数组的方法,最重要的匹配还没有说。那究竟如何匹配呢,还是先看代码:
1 void getnext(void) { 2 next[1]=0; 3 int k=0; 4 for (int i=2;i<=m;++i) { 5 while (k>0 && t[i]!=t[k+1]) k=next[k]; 6 if (t[i]==t[k+1]) k++; 7 next[i]=k; 8 } 9 } 10 11 void kmp(void) { 12 int k=0; 13 for (int i=1;i<=n;++i) { 14 while (k>0 && s[i]!=t[k+1]) k=next[k]; 15 if (s[i]==t[k+1]) k++; 16 if (k==m) { 17 printf("%d ",i-k+1); 18 } 19 } 20 }
是不是和求next数组时很像?实际上求next数组便是模式串的自我匹配。
放一道模板题:https://www.luogu.org/problemnew/show/P3375
代码如下:
1 #include<cstdio> 2 #include<cstring> 3 using namespace std; 4 5 char s[1000001],t[1000001]; 6 int next[1000001],n,m; 7 8 void getnext(void) { 9 next[1]=0; 10 int k=0; 11 for (int i=2;i<=m;++i) { 12 while (k>0 && t[i]!=t[k+1]) k=next[k]; 13 if (t[i]==t[k+1]) k++; 14 next[i]=k; 15 } 16 } 17 18 void kmp(void) { 19 int k=0; 20 for (int i=1;i<=n;++i) { 21 while (k>0 && s[i]!=t[k+1]) k=next[k]; 22 if (s[i]==t[k+1]) k++; 23 if (k==m) { 24 printf("%d\n",i-k+1); 25 } 26 } 27 } 28 29 int main() { 30 scanf("%s%s",s+1,t+1); 31 n=strlen(s+1); m=strlen(t+1); 32 getnext(); 33 kmp(); 34 for (int i=1;i<=m;++i) printf("%d ",next[i]); 35 return 0; 36 }