KMP算法
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
比如2个字符串
S1: babababccb
S2: ababc
现在,我们想要知道,S2这个字符串在S1这个字符串中存在吗?如果存在,请输出所在位置。
->通过肉眼观察,我们很容易发现S2在S1中存在,位置处于4.
在不知道kmp算法之前,我们在处理的时候,采用移位的思想,首先比较2个字符串的第一个字母,如果不相同,那么就移动一位。如果相同,继续比较下面几个字符串,知道S2的所有字符串都相同,那么就匹配成功了。
下图就是模拟传统匹配的过程:
这样的匹配看起来也可以实现我们刚刚的功能,但是每次都要去比较整个串,时间复杂度上是不允许的。
可以举一个极端的例子:
S1:aaaaaaaaaaaaaaaaab
S2:aaaaaab
如果是这个例子,S2在最后匹配成功,前面很多无用的比较都是浪费时间的。时间复杂度为O(n*m)
这时候,kmp就上场了,kmp就是对传统的暴力算法的优化,传统右移每次移动1位,而kmp很聪明,每次移动若干位。
那么,如何实现每次尽可能的多移动位数,从而达到最快速度匹配呢。
kmp在匹配之前,首先计算一个Next数组。这个next数组计算的是,找出最长的相同前缀和后缀。
字符串S2:ababc
子串有:
a 最长的相同前缀和后缀:0
ab 最长的相同前缀和后缀:0
aba 最长的相同前缀和后缀:1
abab 最长的相同前缀和后缀:2
ababc 最长的相同前缀和后缀:0
Next[]={0,0,1,2,0};
求next数组的代码:
void get_next()
{
_next[0]=-1;
//第一个值为-1的意思就是,在上面数组的基础上,都向后偏移一位
//第一个位置填-1,这样写方便后面匹配。
//结果为 [-1,0,0,1,2]
int i=0;
int j=-1;
while(i<m)
{
if(j==-1||t[i]==t[j]) //t可以看做上面的S2
{
i++;
j++;
_next[i]=j;
}
else
{
j=_next[j];
}
}
}
S2=“ababc”
第一次 i=0,j=-1 -> i++,j++,next[1]=0
第二次 i=1,j=0 -> j=next[j] -> j=-1
第三次 i=1,j=-1 -> i++,j++,next[2]=0
.
.
.
.这样模拟下去,就能计算出next数组,不清楚的手工模拟一下就懂了。
接下来就是匹配过程,一张图来解释:
模板题 HDU1711
AC代码+kmp模板:
#include <bits/stdc++.h>
#define rep(i,x,y) for(int i=(x);i<(y);i++)
using namespace std;
int const N=1000000+100;
int T,n,m;
int s[N],t[N];
int _next[N];
void get_next()
{
_next[0]=-1;
int i=0;
int j=-1;
while(i<m)
{
if(j==-1||t[i]==t[j])
{
i++;
j++;
_next[i]=j;
}
else
{
j=_next[j];
}
}
}
int kmp()
{
get_next();
int i=0;
int j=0;
while(i<n)
{
if(j==-1||s[i]==t[j])
{
i++;
j++;
}
else
{
j=_next[j];
}
if(j==m)return i-j+1;
}
return -1;
}
int main()
{
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
rep(i,0,n)scanf("%d",&s[i]);
rep(i,0,m)scanf("%d",&t[i]);
printf("%d\n",kmp());
}
return 0;
}