Q:给定两个字符串A、B,求A在B中出现了多少次
这是KMP的经典问题,我们将从这里开始引入KMP算法


KMP算法の思想

KMP算法分为两步

1.对A进行自我匹配,求出A的nxt数组
其中nxt[i]nxt[i]表示 A中以ii结尾的非前缀子串A的前缀最大匹配长度
即若nxt[i]=jnxt[i]=j,则A[ij+1A[i-j+1~i]=A[1i]=A[1 ~j]j]

2.将A与B匹配,求出数组ff
其中f[i]f[i]表示 B中以ii结尾的子串A的前缀最大匹配长度

煮个栗子
KMP--学习笔记

那么该如何快速求出nxt数组呢
假设我们已经知道nxt[1]nxt[1]~nxt[6]nxt[6],现在求nxt[7]nxt[7]

nxt[7]nxt[7]得过程实际上就是 求一个最大的jj,满足A[1A[1~j]=A[ijj] = A[i-j~6]6]A[j+1]=A[7]A[j+1]=A[7]
然后令nxt[7]=j+1nxt[7]=j+1

对于这个找jj得过程,我们可以按照从大到小的顺序
先找到所有满足第一个条件的jj再判断第二个条件是否成立

对于nxt[7]nxt[7],满足第一个条件的最大的jj显然是j=nxt[6]=4j=nxt[6]=4
然后我们判断第二个条件A[j+1=5]=A[i=7]A[j+1=5]=A[i=7]成立,所以nxt[7]=5nxt[7]=5

在考虑nxt[8]nxt[8],同样先取最大的满足条件一的j=nxt[7]=5j=nxt[7]=5
但这时候发现A[j+1=6]!=A[i=8]A[j+1=6]!=A[i=8],条件二不成立
于是我们再找第二大的满足条件一jj,而这个jj正好就是nxt[nxt[7]]=nxt[5]=3nxt[nxt[7]]=nxt[5]=3
但是A[j+1=4]!=A[i=8]A[j+1=4]!=A[i=8],条件二依然不成立
再找第三大的满足条件一jj,这个jj等于nxt[nxt[nxt[7]]]=nxt[3]=1nxt[nxt[nxt[7]]]=nxt[3]=1
很可惜条件二还是不成立
再找第四大的满足条件一j=nxt[1]=0j=nxt[1]=0,到这里前面已经没有字符了
所以直接判断是否有A[1]=A[i=8]A[1]=A[i=8],这里满足了条件,所以nxt[8]=1nxt[8]=1


通过上面的 栗子 我们已经对KMP的思路有了基本了解
在这里再一次用稍形式化的语言描述一次

假设当前已求出nxt[1]nxt[1]~nxt[i1]nxt[i-1]
现在需要求一个最大的jj,满足A[1A[1~j]=A[ijj] = A[i-j~i1]i-1]A[j+1]=A[i]A[j+1]=A[i]
nxt[i]=j+1nxt[i]=j+1

首先尝试最大的满足条件一的j=nxt[i1]j=nxt[i-1],判断条件二是否成立
若成立则nxt[i]=j+1nxt[i]=j+1,否则j=nxt[j]j=nxt[j],重复上述步骤
j=0j=0时直接判断是否有A[1]=A[i]A[1]=A[i],若是则nxt[i]=1nxt[i]=1,否则等于0

算法的总时间复杂度为O(n+m)O(n+m)


KMPの代码实现

由于一般情况下读入字符串时第一个字符会储存在第0号位
为了方便运算我们将nxt[]表示的值都-1,但记得实际上表示的长度应该再+1

void qnxt(char* ss,int len)
{
    int j=-1; nxt[0]=-1;
    for(int i=1;i<len;++i)
    {
        while(j!=-1&&ss[i]!=ss[j+1]) j=nxt[j];
        if(ss[i]==ss[j+1]) j++;
        nxt[i]=j;
    }
}

求完nxt[]nxt[]数组后在考虑f[]f[]数组
由于其定义的基本相同,所以求解方法已基本一致
注意为了运算方便保存在f[]f[]中的值也是减了1的

void KMP(char* a,char* b)
{
    int n=strlen(a),m=strlen(b);
    int j=-1; qnxt(a,n);
    for(int i=0;i<m;++i)
    {
        while(j!=-1&&b[i]!=a[j+1]) j=nxt[j];
        if(b[i]==a[j+1]) j++;
        f[i]=j;
    }	
}

再次提醒,真正的nxt[]nxt[]f[]f[]保存的值实际应为

for(int i=0;i<strlen(A);++i)
printf("%d ",nxt[i]+1);

for(int i=0;i<strlen(B);++i)
printf("%d ",f[i]+1);

KMPの应用

POJ - 3461 Oulipo

现在再次回到开头的问题 (是不是都快忘掉了)
Q:给定两个字符串A、B,求A在B中出现了多少次

A:求出B的f[]f[]数组后
若有f[i]=nf[i]=n,那么A在B中的一个出现位置就是B[in+1B[i-n+1~i]i]

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=2000010;
int Q;
char txt[maxn],pat[maxn];
int nxt[maxn];

void qnxt(char* ss,int len)
{
	int j=-1; nxt[0]=-1;
	for(int i=1;i<len;++i)
	{
		while(j!=-1&&ss[i]!=ss[j+1]) j=nxt[j];
		if(ss[i]==ss[j+1]) ++j;
		nxt[i]=j;
	}
}

int KMP(char* a,char* b)
{
	int res=0;
	int n=strlen(a),m=strlen(b);
	int j=-1; qnxt(a,n);
	for(int i=0;i<m;++i)
	{
		while(j!=-1&&b[i]!=a[j+1]) j=nxt[j];
		if(b[i]==a[j+1]) ++j;
		if(j==n-1) res++;//f[i]=j
	}
	return res;
}

int main()
{
    Q=read();
    while(Q--)
    {
    	scanf("%s%s",&pat,&txt);
    	printf("%d\n",KMP(pat,txt));
	}
}

POJ - 2752 Seek the Name, Seek the Fame

Q:给定字符串S,求S中既是前缀也是后缀的子串的所有可能长度

A:求出S的nxt数组
j=lenj=len,不断迭代j=nxt[j]j=nxt[j]直到j=0j=0,遍历到的数即为所求
注意这样得到的顺序时降序的,还需要存入栈中再输出

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long lt;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=1000010;
char ss[maxn];
int nxt[maxn],vis[maxn];
int st[maxn],top;

void qnxt(char* a,int len)
{
    int j=-1; nxt[0]=-1;
    for(int i=1;i<len;++i)
    {
        while(j!=-1&&a[i]!=a[j+1]) j=nxt[j];
        if(a[i]==a[j+1]) j++;
        nxt[i]=j;
    }
}

int main()
{
    
	while(scanf("%s",&ss)!=EOF)
    {
    	int len=strlen(ss); top=0;
		qnxt(ss,len);

        int j=len-1;
		while(j!=-1){
		    st[++top]=j+1;
		    j=nxt[j];
		}
		while(top) printf("%d ",st[top--]);
		printf("\n");
	}
	return 0;
}

相关文章: