第一次写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$ ←退回到第三位,再试试
$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\n",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\n",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;
}