浅谈扩展KMP算法

前言

首先,kmp算法大家都知道,但是听到扩展kmp的时候,会想到底是干什么的?
那么扩展kmp算法是用来求解下面问题的:

给定母串S,和子串T。
定义 n=|S| , m=|T| extend[i]=S[i..n] 与T的最长公共前缀长度。请在线性的时间复杂度内,求出所有的 extend[1..n]

我们其实可以知道,如果有某个位置i满足 extend[i]=m ,那么T就肯定在S中出现过,并且进一步知道出现首位置是i——而这正是经典的KMP问题。
因此可见“扩展的KMP问题”是对经典KMP问题的一个扩充和加难

算法描述

在解上面这个问题前我们要先解决一个类似的问题:求字符串s的所有后缀和s本身的最长公共前缀;
我们用next[]数组保存这些值;
现在我们假设要求 next[x] ,并且 next[i]0<i<x 的值都已经求出;
我们设 p=k+next[k]1 , k是使p最大的i( 0<i<x );如图:

这里写图片描述

现在整理一下所解问题:
已知: s[k..p]==s[0..next[k]1] ,求 s[x..n1] s[0..n1] 的最长公共前缀;
s[k..p]==s[0..next[k]1] 得:
s[x..p]==s[xk..next[k]1] ———1//这个是显然的
并设 L1=px+1 ;
因为 xk 肯定是小于x的所以 L2=next[xk] 是已知的,得:
s[0..L21]==s[xk..xk+L21] ; ——–2
通过等式1,2可以推出 s[0..k1]==s[x..k2]
L1L2 如下图
这里写图片描述
此时表示 s[0..L11]==s[x..x+L11] 但不能确定蓝色部分是否相等,所以需要继续比下去

L1>L2 如下图:
这里写图片描述

此时表示 s[0..L21]==s[x..x+L21] 而且因为 L2=next[xk] 使得 s[L2]!=s[x+L2]
所以 next[x]=L2 ;

回到原来的问题

假设此时已经求出 next[] ,我们用 extend[] 保存字符串S的所有后缀和字符串T的最长公共前缀的值
我们重复上面的过程:
现在我们假设要求 extend[x] ,并且 extend[i]0<i<x 的值都已经求出;
我们设 p=k+extend[k]1 , k是使p最大的 i ( 0<i<x );如图:
这里写图片描述

现在整理一下问题:

已知: s[k..p]==T[0..extend[k]1] ,求 s[x..n1] T[0..m1] 的最长公共前缀;
s[k..p]==T[0..extend[k]1] 得:
s[x..p]==T[xk..extend[k]1] ———1//同样这个是显然的
并设 L1=px+1 ;
因为 xk 肯定是小于x的所以 L2=next[xk] 是已知的,得:
T[0..L21]==T[xk..xk+L21]; ——–2
通过等式1,2可以推出 T[0..k1]==s[x..k2]
L1L2 如下图
这里写图片描述
表示 T[0..L11]==s[x..x+L11] 但不能确定蓝色部分是否相等,所以需要继续比下去

L1>L2 如下图:
这里写图片描述
表示 T[0..L21]==s[x..x+L21] 而且因为 L2=extend[xk] 使得 T[L2]!=s[x+L2]
所以 extend[x]=L2 ;

如果你仔细阅读的话可能会发现并没有说 next[] 怎么求,我们发现计算next实际上以T为母串、T为子串的一个特殊“扩展的KMP”。具体可以参考标准KMP的求法就行了。

复杂度分析

容易看出,在计算的过程中,凡是访问过的点,都不需要重新访问了。一旦比较,都是比较以前从不曾探访过的点开始。因此总的时间复杂度是 O(n+m) ,是线性的。

应用例题

这里有一道例题可以练手:HDU4333

题意:给一个数字,每一次把它的最后一位拿到最前面,一直那样下去,分别求形成的数字小于,等于和大于原来数的个数。
例如:134可以形成134,341,413三个数,所以分别是1,1,1。
数据范围:50组以内数据,数字大小在 10100000 以内。

由于长度为len的字符串形成题目要求的串的个数为len,那么我们可以把原来的两个串T连接起来形成字符串S,然后找S的每个后缀的前len个元素即可。
这里主要是如何比较的问题,对于字符串的比较,我们可以先求出他们的最长公共前缀长度,然后只需要比较一次就可以知道结果了。那么最长公共前缀怎么求,由于这里是一个串T与另一个串S,来求S的所有后缀与T的最长公共前缀长度,所以用扩展KMP。如果 extend[i]len ,就说明与原来的相等了,否则如果 S[i+extend[i]]<T[extend[i]] 就说明小于,否则就是大于。

另外还推荐一道题目POJ2185

代码

代码是上面的例题的代码,同时中间的函数也可以当成模板来使用。

#include <cstdio>
#include <cstring>
#include <algorithm>

const int size = 500000+10;
int next[size],nextval[size],extend[size];
char s[size],t[size];

void Getnext(char *T) {
    int a=0,Tlen=strlen(T);
    next[0]=Tlen;
    while(a<Tlen-1&&T[a]==T[a+1]) a++;
    next[1]=a;a=1;
    for(int k=2;k<Tlen;k++) {
        int p=a+next[a]-1,L=next[k-a];
        if((k-1)+L>=p) {
            int j=(p-k+1)>0?p-k+1:0;
            while(k+j<Tlen&&T[k+j]==T[j]) j++;
            next[k]=j;a=k;
        }
        else next[k]=L;
    }
}

void Getextend(char *s,char *t) {
    int a=0;Getnext(t);
    int Slen=strlen(s),Tlen=strlen(t);
    int Minlen=std::min(Slen,Tlen);
    while(a<Minlen&&s[a]==t[a]) a++;
    extend[0]=a;a=0;
    for(int k=1;k<Slen;k++) {
        int p=a+extend[a]-1,L=next[k-a];
        if((k-1)+L>=p) {
            int j=(p-k+1)>0?p-k+1:0;
            while(k+j<Slen&&j<Tlen&&s[k+j]==t[j]) j++;
            extend[k]=j;a=k;
        }
        else extend[k]=L;
    }
}

void Nextval(char *t) {
    int i=0,j=-1;nextval[0]=-1;
    int Tlen=strlen(t);
    while(i<Tlen) {
        if(j==-1||t[i]==t[j]) {
            i++;j++;if(t[i]!=t[j]) nextval[i]=j;
            else nextval[i]=nextval[j];
        }
        else j=nextval[j];
    }
}

int Slen,Tlen;

int main()
{
    int Slen,Tlen,T=1,tt=1;
    scanf("%d",&T);
    while(T--) {
        scanf("%s",s);strcpy(t,s);
        strcat(s,t);Getextend(s,t);
        Tlen=strlen(t);Slen=strlen(s);
        Nextval(t);
        int MOD=Tlen-nextval[Tlen],temp=1;
        if(Tlen%MOD==0) temp=Tlen/MOD;
        int ans1=0,ans2=0,ans3=0;
        for(int i=0;i<Tlen;i++) {
            if(extend[i]>=Tlen) ans2++;
            else if(s[i+extend[i]]<t[extend[i]]) ans1++;
            else ans3++;
        }
        printf("Case %d: %d %d %d\n",tt++,ans1/temp,ans2/temp,ans3/temp);
    }
    return 0;
}

最后

针对字符串处理的许多算法,我们考虑的时候能够尽量保证:已经访问过的点绝不再访问,充分利用已经得到的信息。,这也是扩展kmp算法的魅力之所在。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值