KMP算法

来源:互联网 发布:南京行知基地卫生间 编辑:程序博客网 时间:2024/05/11 12:13

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