KMP算法

字符串比较,是实际应用中比较重要的一个内容,如果单纯的逐位比较,显然是太慢了(当然,朴素的字符串比较算法在平均情况下表现还是很好的)。下面要介绍的是KMP算法。

思想

KMP算法的思想很简单,它最核心的思想就是当失配时利用当前的信息而不是重新从头比较。考虑A串和B串,要求B在A第一次出现的位置(通常称B串为模式串)。令p[i]表示小于i的最大的x,使得[1,x]=[i-x+1,i](如何求p数组的问题等下再探讨)。那么,如果失配(A[i]≠B[j]),在这种情况下,朴素算法将令j=1,重新比较,但是,KMP算法利用了已经处理过的信息,即 B[1,j1]=A[ij+1,i1] ,所以,不需要重新比较,我们就可以知 B[1,p[j1]]=A[ip[j1],i1] ,我们的目的,就是要找到B的一个最大的前缀[1,x],使得 B[1,x]=A[ix+1,i] ,所以,只要不停地重复取p[],直到 B[p[j]+1]=A[i] 即可。代码如下:

int j=0;
for(int i=1;i<=n;i++){
    while(j&&s1[i]!=s2[j+1])j=p[j];
    if(cmp(s2[j+1],s1[i]))j++;
    if(j==m){
        printf("%d",i-m+1);
        return 0;
    }
}

如何求p数组

要求p[i]我们可以利用p[1..i-1],我们知道,p[i]表示的是使前缀等于后缀的最大长度,所以,最好的情况一定是 B[p[i1]+1]=B[i] ,这样,最大长度就是 p[i1]+1 ,但是, B[p[i1]+1] 不一定等于 B[i] ,如果不等于,怎么办,显然,我们需要所小长度,但是,缩小到多少呢?答案是应该缩小到p[p[i-1]]。代码如下:

p[1]=0;
for(int i=2;i<=m;i++){
    int j=p[i-1];
    while(j&&!cmp(s2[j+1],s2[i]))j=p[j];
    if(cmp(s2[j+1],s2[i]))p[i]=j+1;
}

有没有发现,这段求P数组的代码和上面的比较的代码惊人的相似。这是因为,求P数组实际上就是一个对模式串进行自我匹配的过程。

时间复杂度

KMP的时间复杂度是线性的,即是 O(N) ,N为主串长度。为什么?最难估算复杂度的地方就是循环中的while语句,我们看,每次while,j都会变小,并且,j变到0就停了,要变小,j首先得加上去,由于每次循环j最多加一,所以j最多累积加了N,同理,j最多累积减小了N次,所以KMP算法的构造P数组和求解的过程复杂度都为 O(N)
代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 100006
using namespace std;
inline char nc(){
    static char buf[100000],*i=buf,*j=buf;
    return i==j&&(j=(i=buf)+fread(buf,1,100000,stdin),i==j)?EOF:*i++;
}
inline int read_s(char *x){
    char ch=nc();int len=0;
    while(ch!='\n'&&ch!=EOF)*x++=ch,len++,ch=nc();
    *x='\0';
    return len;
}
int n,m,p[maxn];
char s1[maxn],s2[maxn];
int main(){
    freopen("str.in","r",stdin);
    freopen("str.out","w",stdout);
    n=read_s(s1+1);m=read_s(s2+1);
    p[1]=0;
    for(int i=2;i<=m;i++){
        int j=p[i-1];
        while(j&&s2[j+1]!=s2[i])j=p[j];
        if(s2[j+1]==s2[i])p[i]=j+1;
    }
    int j=0;
    for(int i=1;i<=n;i++){
        while(j&&s2[j+1]!=s1[i])j=p[j];
        if(s2[j+1]==s1[i])j++;
        if(j==m){
            printf("%d",i-m+1);
            return 0;
        }
    }
    printf("-1");
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值