第一次写kmp是2月,写错但AC了...第二次是6月,才发现...
现在是8月,第三次 /cy
KMP (D.E.Knuth - J.H.Morris - V.R.Pratt) 也叫看…,是一种改进的字符串匹配算法,核心是在匹配失败后减少已经匹配过的部分重新匹配。
原理
KMP是通过一个f[](fail)——或者叫next的“失配函数”来实现的。
把已经给出的串称为文本串,要在文本串中找的串称为模式串。
首先,求出模式串的失配函数。
fail函数的含义是:在到这一位为止,前缀=后缀的最长长度是多少。
这里的=是指完全相同,比如ABCABC这样的,而不是对称,并且可以有重叠。
特别地,前两位(f[0],f[1])为零。
比如串$ABABAC$,它的fail应该为$001230$ (ABA和ABA重叠了也没事)。
假设已经求好了fail,那么,假设有文本串$ABABABAC$。
$ABABABAC$
$ABABAC$ ←到这里,发现配不上了
$ABABABAC$
$ABABAC$ ←退回到第三位,再试试
实现
求失配函数
求失配函数是一个自己和自己匹配的过程。
void getf() { f[0] = f[1] = 0; for(int i = 1; i < len2; i++) { int j = f[i]; while(j && p[i]!=p[j]) j = f[j]; if(p[i] == p[j]) f[i+1] = j+1; else f[i+1] = 0; } }
已知第i位的失配函数f[i],要求第i+1位的。
设j=f[i]。f[i]一定在i前面,f[i]已经求好了。
如果字符串的i+1和j+1位不同(p[i]≠p[j]),那么j不断地跳到自己的失配函数f[j],直到匹配上或j=0为止。
如果匹配上,f[i+1] = j+1;
否则如果还是p[i]≠p[j],说明是j=0而不是匹配上了,那么f[j] = 0。
匹配
void kmp() { int j = 0; for(int i = 0; i < len1; i++) { while(j && s[i]!=p[j])j = f[j]; if(s[i] == p[j])j++; if(j == len2) { printf("%d ",1+i-len2+1); j = f[j]; } } }
文本串指针i,模式串指针j。
和刚刚类似,如果没匹配上,就不断把j跳到f[j]。
如果匹配上了,j往后一位,即j++。
完整代码如下
#include<cstdio> #include<iostream> #include<cmath> #include<cstring> #define MogeKo qwq using namespace std; #include<string> const int maxn = 1e6+10; int len1,len2,f[maxn]; string s,p; void getf() { f[0] = f[1] = 0; for(int i = 1; i < len2; i++) { int j = f[i]; while(j && p[i]!=p[j]) j = f[j]; if(p[i] == p[j]) f[i+1] = j+1; else f[i+1] = 0; } } void kmp() { int j = 0; for(int i = 0; i < len1; i++) { while(j && s[i]!=p[j])j = f[j]; if(s[i] == p[j])j++; if(j == len2) { printf("%d ",1+i-len2+1); j = f[j]; } } } int main() { cin>>s>>p; len1 = s.length(); len2 = p.length(); getf(); kmp(); for(int i = 1; i <= len2; i++) printf("%d ",f[i]); return 0; }