kmp的简单理解

参考的是:http://www.matrix67.com/blog/archives/115

kmp算法就是解决两个字符串,目标文本T,关键词字符串S,匹配的问题。

比如  T:abababcdabc     S:abc   在T里找出连续的子字符串是S的位置

先看看完全暴力的算法:

<span style="font-size:18px;">void violent(const char  *T,const char *S)//将完全匹配的子字符串的起始位置存入position[]数组中
{
    int lenT=strlen(T);
    int lenS=strlen(S);
    tot=0;
    for(int i=0;i<lenT;i++)
    {
        int j;
        for(j=0;j<lenS;j++)
            if(T[i+j]!=S[j])
                break;
        if(j==lenS)position[tot++]=i;
    }
}
</span>

就是枚举T里面的起始位置,再一步一步和S匹配啦。

当然其中的 i<lenT 可以优化为 i<lenT-lenS+1 最坏的复杂度:O((lenT-lenS+1)*lenS)

复杂度有点高,然后Knuth、Morris、Pratt三个人就提出了KMP算法。

在暴力算法中,有的字符比较是没有意义的(手动模拟即会发现)

例如:

      T:ababc    S:aba

当i=1,j=0 时不用比较就会知道他们是不可能匹配的

KMP算法就是利用减少这些无效的比较来降低复杂度的。

看看KMP的流程

     T:ababca   S:baba

将  T  看成是固定的。

不断向右移动 S 观察是否匹配

当匹配到

     T: a b a b | c a

    S:     b a b | a

i=1,j=3时 未匹配  (这里的 i 的含义是  将S的首部移动到了 T 中 T[i] 的位置)

KMP算法直接转移到了   

     T: a b a b | c a

     S:          b | a b a 

跳过了i=2的步骤,为什么可以跳?因为已经比过了。当i=1,j=1时T[i+j]==S[j]  ,T[ i+j=2 ]已经比过了!,S直接向右滑行,直到T[2]再次匹配。这里可能产生了误区,再看一个例子:

     T: b a b a b | c a

     S: b a b a b | d

下一步可直接变成

    T: b a b a b | c a

    S:       b a b | a b d

将S向右滑行 直到  |  左边的S子字符串和  T中 |  左边的完全匹配,用表达式来描述就是:

当T[i-1]=S[j]且T[i]!=S[j+1]时,j=len ,S[k]=S[j-k],0<=k<=len,且len!=j,取j=max(len);发现 j 的下一个取值只与S相关,预处理即可,将对于每一个j ,j的下一个取值存入P[]中

运行部分

void KMP(const char  *T,const char *S)
{
    int lenT=strlen(T);
    int lenS=strlen(S);
    KMP_solveS(S,P);//当字符串S多次使用时,此函数应放在外面
    tot=0;
    int i,j=-1;//假设T[-1]=S[-1],只是个假设....
    for(int i=0;i<lenT;i++)
    {
        while(j>=0&&S[j+1]!=T[i])
            j=P[j];
        if(S[j+1]==T[i])
            j=j+1;
        if(j==lenS-1)
        {
            position[tot++]=i-lenS+1;
            j=P[j];//可能有多个匹配
        }
    }
}

O(lenT)的,那个while()在整个函数里总的运行次数最多为lenT

当然喽,i<lenT ,也可以优化为  i-1+lenS-j-1<lenT

预处理部分

void KMP_solveS(const char *S,int *P)//实质是自我匹配的过程,该函数将S的信息存放在P中
{
    int lenS=strlen(S);
    P[0]=-1;
    int j=-1;
    for(int i=1;i<lenS;i++)
    {
        while(j>=0&&S[j+1]!=S[i])
            j=P[j];
        if(S[j+1]==S[i])
            j=j+1;
        P[i]=j;
    }
}
和上面的是类似的  O(lenS)

总的复杂度:O(lenS+lenT)

给个总的

<span style="font-size:18px;">/*
KMP算法 : 解决字符串匹配问题
*/

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int const MAXN=1000+10;

char S[MAXN];
char T[MAXN];//目标文本

int position[MAXN],tot;//记录所有完全匹配的初始位置

//暴力测试函数

void violent(const char  *T,const char *S)
{
    int lenT=strlen(T);
    int lenS=strlen(S);
    tot=0;
    for(int i=0;i<lenT-lenS+1;i++)//i<lenT
    {
        int j;
        for(j=0;j<lenS;j++)
            if(T[i+j]!=S[j])
                break;
        if(j==lenS)position[tot++]=i;
    }
}

int P[MAXN];//P[i]  存的是对于 字符串S   S[k]=S[i-len+k],0<=k<=len,存在len使所有的k均满足等式,且取P[i]=最大的len;若不存在则P[i]=-1;

void KMP_solveS(const char *S,int *P)//实质是自我匹配的过程,该函数将S的信息存放在P中
{
    int lenS=strlen(S);
    P[0]=-1;
    int j=-1;
    for(int i=1;i<lenS;i++)
    {
        while(j>=0&&S[j+1]!=S[i])
            j=P[j];
        if(S[j+1]==S[i])
            j=j+1;
        P[i]=j;
    }
}
void KMP(const char  *T,const char *S)
{
    int lenT=strlen(T);
    int lenS=strlen(S);
    KMP_solveS(S,P);//当字符串S多次使用时,此函数应放在外面
    tot=0;
    int i,j=-1;
    for(int i=0;i-1+lenS-j-1<lenT;i++)//i<lenT
    {
        while(j>=0&&S[j+1]!=T[i])
            j=P[j];
        if(S[j+1]==T[i])
            j=j+1;
        if(j==lenS-1)
        {
            position[tot++]=i-lenS+1;
            j=P[j];
        }
    }
}
int main()
{
    //T:目标文本   S:关键字 文本
    while(scanf("%s%s",T,S)!=EOF)
    {
        printf("after violent\n");
        violent(T,S);
        for(int i=0;i<tot;i++)
            printf("%d ",position[i]);
        printf("\n\n");

        printf("after my kmp\n");
        KMP(T,S);
        for(int i=0;i<tot;i++)
            printf("%d ",position[i]);
        printf("\n\n");

    }
    return 0;
}

初学kmp,欢迎各位指正




 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值