字符串匹配算法KMP
KMP是啥
一种基础的字符串匹配算法,是学习字符串算法的基础
很多人都是以这个算法为起点开始学习字符串的
KMP的作用
求出模式串在文本串里出现了多少次,以及在哪里出现过
KMP的实现
暴力
假如直接暴力匹配的话,复杂度\(O(nm)\)(n是模式串长度,m是文本串长度)
明显T飞
于是我们就有了KMP算法
核心部分
先上定义部分:
int kmp[2000001];//kmp中的指针
char a[2000001],b[2000001];//a是文本串,b是模式串
然后我们现在来看一看什么是kmp里面的kmp数组。
- kmp失配指针
这个失配指针就是上面的kmp数组了
这个kmp是什么意思呢?这个kmp[i]代表的是,在[1..i]这个子串中,前缀与后缀的最长长度
- kmp第一步:自配
for(int i=2;i<=l2;++i){//l2是模式串长度
while(j&&b[i]!=b[j+1]){//一会儿解释
j=kmp[j];
}
if(b[i]==b[j+1])j++;
kmp[i]=j;
}
首先定义前缀i代表子串[1..i],后缀i代表子串[j..len]
这其中的\(while(j&&b[i]!=b[j+1])\)是怎么回事儿呢?
首先我们要知道,j就是前缀i-1的前缀与后缀的最长相同长度
那么我们首先看一看目前走到的第i位与第j+1位相不相同
假如相同,那么前缀j+1就与后缀[i-j..i]相同了
假如不同,那么我们跳到下一个相同的地方。
然后不断地沿着kmp跳,直到跳到相同的地方。假如没有相同的,那这个kmp[i]就为0了。
如下图所示:
那么这里的i为什么不能取1呢?
大家可以自己试一试,如果取1的话就死循环了
- kmp第二步 在原串中匹配
for(int i=1;i<=l1;++i){//l1是文本串长度
while(j&&a[i]!=b[j+1]){
j=kmp[j];
}
if(a[i]==b[j+1])j++;
if(j==l2){
printf("%d\n",i-l2+1);
j=kmp[j];
}
}
有关于\(while(j&&a[i]!=b[j+1])\),这就是说,我们现在匹配到的第i位与b中的第j+1位不符合的话,
我们就找到前缀j+1的前缀与后缀的最长相同长度,然后从已经匹配好的前缀跳到相同的后缀上
大概就是这幅图:
kmp到这里就结束啦~
- 完整代码(洛谷P3375 【模板】KMP字符串匹配)
#include<bits/stdc++.h>
using namespace std;
int kmp[2000001];
char a[2000001],b[2000001];
int main(){
scanf("%s",a+1);
scanf("%s",b+1);
int l1=strlen(a+1),l2=strlen(b+1);
int j=0;
for(int i=2;i<=l2;++i){
while(j&&b[i]!=b[j+1]){
j=kmp[j];
}
if(b[i]==b[j+1])j++;
kmp[i]=j;
}
j=0;
for(int i=1;i<=l1;++i){
while(j&&a[i]!=b[j+1]){
j=kmp[j];
}
if(a[i]==b[j+1])j++;
if(j==l2){
printf("%d\n",i-l2+1);
j=kmp[j];
}
}
for(int i=1;i<=l2;++i)printf("%d ",kmp[i]);//题目要求输出kmp数组
putchar('\n');
}
- 复杂度
时间复杂度是\(O(n+m)\)的(这不比朴素算法快多了?)
跑起来飞快(huaji)
EXKMP(扩展KMP)
简介
这个算法用来求一个串的每个后缀与另一个串的\(LCP\),即最长公共前缀
然而似乎与kmp没有什么关系
有关于这个算法,我当时并没有怎么学
而且貌似是一个边缘算法,不怎么会考
所以贴个模板跑路吧
#include<bits/stdc++.h>
using namespace std;
int nxt[2000001];
int ext[2000001];
void pre(char s[]){
int m=strlen(s);
int j=0,p=0;
nxt[0]=m;
for(int i=1;i<m;++i){
if(i>=p||i+nxt[i-j]>=p){
if(i>=p)p=i;
while(p<m&&s[p]==s[p-i])p++;
nxt[i]=p-i;
j=i;
}
else nxt[i]=nxt[i-j];
}
//for(int i=0;i<m;++i)printf("%d ",nxt[i]);
//putchar('\n');
}
void getext(char s[],char t[]){
int n=strlen(s);
int m=strlen(t);
int j=0,p=0;
pre(t);
for(int i=0;i<n;++i){
if(i>=p||i+nxt[i-j]>=p){
if(i>=p)p=i;
while(p<n&&p-i<m&&s[p]==t[p-i])p++;
ext[i]=p-i;
j=i;
}
else ext[i]=nxt[i-j];
//cout<<ext[i]<<" ";
}
}
char s[2000001],t[2000001];
int main(){
scanf("%s%s",s,t);
getext(s,t);
int q;
scanf("%d",&q);
for(int i=1;i<=q;++i){
int tmp;
scanf("%d",&tmp);
printf("%d\n",ext[tmp-1]);
}
}