KMP算法是一种字符串匹配算法,由三个老外发现的,因此算法就用三个老外名字首字母命名了。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
KMP算法的关键是next数组的求解,并根据实际情况next数组可以进行各种活用。
next数组是什么?
例如有一个字符串n,和一个字符串m,需要在n中匹配m,那么就需要对m进行next数组求解,next数组就是字符串的自匹配,返回失配位之前的最长公共前后缀!这样就利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。
KMP 算法之所以难懂,很大一部分原因是很多实现的方法在一些细节的差异,重要的是领悟思想,根据实际情况求next数组。我浏览过网上的一些博文,求解next数组的形式很多,有的next数组时从0开始的,有的是从1开始的,有的next数组返回的是之前字符串的长度,有的返回是的之前字符串的下标,不过根据我的理解就是,next数组最好从1开始,因为某些情况下,从0开始的next数组不符合需要!
下面举例说一下为什么:
字符串"aa",如果next数组从0开始的话,那么next[0]=-1 next[1]=0,如果next数组从1开始的话,那么next[0]=-1 next[1]=0 next[2]=1。
而对于字符串"ab",如果next数组从0开始的话,那么next[0]=-1 next[1]=0,如果next数组从1开始的话,那么next[0]=-1 next[1]=0 next[2]=0。
从上面可以看出,从0开始的话,无论第二个字母是什么,next[1]都是0,如果我需要求解这个字符串的对称的最大长度,那么从0开始的next数组显然不符合要求。
下面开始介绍next数组的作用以及如何求next数组,还是举字符串m为例,下面的图就是求next数组的过程,可以看出字符串m的前后有两部分是对称的,这就是自匹配,对于字符串m的每一个位置求next的值,如果字符串n和字符串m匹配的时候,在某个位置失配,利用next数组,下一次就不需要在m的开始位置重新匹配
下面看一下字符串n和字符串m匹配中的一环,长的字符串为n,短的是m,红色部分为失配位置,可以看出利用next数组,在n和m失配的时候,下一次匹配,m就不需要从头开始与n匹配。
求解next数组的重点就是继承
a 、当前面字符的前一个字符的对称程度为 0 的时候,只要将当前字符与子串第一个字符进行比较。这个很好理解啊,前面都是 0 ,说明都不对称了,如果多加了一个字符,要对称的话最多是当前的和第一个对称。比如 agcta 这个里面 t 的是 0 ,那么后面的 a 的对称程度只需要看它是不是等于第一个字符 a 了。
b 、按照这个推理,我们就可以总结一个规律,不仅前面是 0 呀,如果前面一个字符的 next 值是 1 ,那么我们就把当前字符与子串第二个字符进行比较,因为前面的是 1 ,说明前面的字符已经和第一个相等了,如果这个又与第二个相等了,说明对称程度就是 2 了。有两个字符对称了。比如上面 agctag ,倒数第二个 a 的 next 是 1 ,说明它和第一个 a 对称了,接着我们就把最后一个 g 与第二个 g 比较,又相等,自然对称成都就累加了,就是 2 了。
c 、按照上面的推理,如果一直相等,就一直累加。
当然不可能会那么顺利让我们一直对称下去,如果遇到下一个不相等了,那么说明不能继承前面的对称性了,这种情况只能说明没有那么多对称了,但是不能说明一点对称性都没有,所以遇到这种情况就要重新来考虑,这个也是难点所在。
如果不相同,用一句话来说,就是:
从前面来找子前后缀
1 、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。
2 、要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。
就像上面这张图,红色部分是当前的求解位置,如果红色部分和黄色部分的第一个字母不相等,则没有办法继承(蓝+绿+蓝)这部分的长度,但是却可以继承更小的长度,就是红色部分前面的一小部分蓝色
下面给出kmp的模板
//这里的str数组从下表0开始,如果希望从1开始则需要修改一下kmp函数
char str[1000005],str2[10005];
int nextArr[10005];
int T,m,n;
void count_nextArr(){
int j=-1,i=0;
nextArr[0]=-1;
while(i<m){
if(j==-1 || str2[i]==str2[j]){
nextArr[++i]=++j;
} else{
j=nextArr[j];
}
}
}
int kmp(){
int i=0,j=0;
while(i<n && j<m){
if(str[i] == str2[j]){
i++,j++;
} else {
if(j==0) i++;
else j = nextArr[j];
}
}
return (j==m) ? i-m+1 : -1;
}
hdu1711:模板题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
int a[1000005],b[10005],nextArr[10005];
int T,m,n;
void count_nextArr(){
int j=-1,i=0;
nextArr[0]=-1;
while(i<m){
if(j==-1 || b[i]==b[j]){
nextArr[++i]=++j;
} else{
j=nextArr[j];
}
}
}
int kmp(){
int i=0,j=0;
while(i<n && j<m){
if(a[i] == b[j]){
i++,j++;
} else {
if(j==0) i++;
else j = nextArr[j];
}
}
return (j==m) ? i-m+1 : -1;
}
int main()
{
//freopen("input.txt","r",stdin);
cin>>T;
while(T--){
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
for(int j=0;j<m;j++){
scanf("%d",&b[j]);
}
count_nextArr();
printf("%d\n",kmp());
}
return 0;
}
hdu2203:kmp入门问题
只需变换一下,将原字符串变成两倍长再和模式串进行匹配,就可以遍历所有的轮回串。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
char str[200005];
char str2[100005];
int nextArr[100005];
int n,m;
void count_nextArr(){
int j=-1,i=0;
nextArr[0]=-1;
while(i<m){
if(j==-1 || str2[i]==str2[j]){
nextArr[++i]=++j;
} else{
j=nextArr[j];
}
}
}
void kmp(){
int i=0,j=0;
while(i<2*n && j<m){
if(str[i] == str2[j]){
i++,j++;
} else{
if(j==0){
i++;
} else{
j = nextArr[j];
}
}
}
if(j!=m){
printf("no\n");
} else{
printf("yes\n");
}
}
int main()
{
//freopen("input.txt","r",stdin);
while(~scanf("%s",str)) {
scanf("%s",str2);
n = strlen(str);
m = strlen(str2);
for(int i=n,j=0;i<2*n;i++,j++){
str[i] = str[j];
}
str[2*n] = '\0';
count_nextArr();
kmp();
}
return 0;
}
hdu2594:kmp入门问题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#define INF 0x3f3f3f3f
using namespace std;
char str[50005],str2[50005];
int nextArr[50005];
int n,m;
void count_nextArr(){
int j=-1,i=0;
nextArr[0]=-1;
while(i<m+n){
if(j==-1 || str[i]==str[j]){
nextArr[++i]=++j;
} else{
j=nextArr[j];
}
}
}
int main()
{
// freopen("input.txt","r",stdin);
while(~scanf("%s",str)){
scanf("%s",str2);
n=strlen(str);
m=strlen(str2);
strcat(str,str2);
count_nextArr();
int len=m+n;
while(nextArr[len]>m || nextArr[len]>n){
len=nextArr[len];
}
for(int i=0;i<nextArr[len];i++){
printf("%c",str[i]);
}
if(nextArr[len]) printf(" ");
printf("%d\n",nextArr[len]);
}
return 0;
}
http://www.cnblogs.com/wuyiqi/archive/2012/01/05/2313746.html