KMP--学习笔记

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/niiick/article/details/83188848

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


KMP算法の思想

KMP算法分为两步

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

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

煮个栗子
在这里插入图片描述

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

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

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

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

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


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

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

首先尝试最大的满足条件一的 j = n x t [ i 1 ] j=nxt[i-1] ,判断条件二是否成立
若成立则 n x t [ i ] = j + 1 nxt[i]=j+1 ,否则 j = n x t [ j ] j=nxt[j] ,重复上述步骤
j = 0 j=0 时直接判断是否有 A [ 1 ] = A [ i ] A[1]=A[i] ,若是则 n x t [ i ] = 1 nxt[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;
    }
}

求完 n x t [ ] 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;
    }	
}

再次提醒,真正的 n x t [ ] 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 ] = n f[i]=n ,那么A在B中的一个出现位置就是 B [ i n + 1 B[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 = l e n j=len ,不断迭代 j = n x t [ j ] j=nxt[j] 直到 j = 0 j=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;
}

猜你喜欢

转载自blog.csdn.net/niiick/article/details/83188848
今日推荐