kmp算法

1. 朴素匹配算法

                   过程如图:
           
          
        
     
翻译成代码:
int index_bf(char *s, char *p)
{
    if(s==NULL || p==NULL)
        return -1;
    int i=0,j=0;
    while(s[i+j]!='\0' && p[j]!='\0')
    {
        if(s[i+j]==p[j])
            j++;
        else
        {
            j=0; i++;
        }
    }
    if(p[j]=='\0')
        return i;
    else
        return -1;
}

2. kmp匹配过程

如图:
        
 1. 可以看到匹配过程中 i 跟 j 是同时增加的,不同于朴素匹配算法只增加 j 。 在 i=5, j=5的时候有c!=d,这个时候的处理过程如下图
       
2. 这里模式串被右移了3个位置,实际上就是j=5变成j=2( next[5]=2 为什么??)。i=5保持不变,继续匹配直到i: 5->8, j:2->5。上图绿色部分ab两个字符我们没有比较过,因为 next[5]=2隐含了前面2个字符不需要在比较的意思。
     

3. next[j]=k的含义

              1. 直观的含义是::设置j=k, 即把模式串右移j-k个位置。 如:next[5]=2,表示j=2即模式串右移5-2=3个位置。
              2. 更进一步的含义是:模式串前面k的字符已经比较过,不需要再比较了,直接从j=k(因为下标从0开始)这个位置开始比较
              3. 再进一步的含义可以用下面的图表示( next[5]=2 ):我们发现了在k=2这个位置,把模式P[0-4]分成了3个部分 "P[0]P[1]"="P[3]P[4]"还有个就是P[2]这3个部分。
       
  举个列子上面的next[5]=2是因为:
       
               4. 用一个比较专业的术语来描述就是,在P[0~j-1]中找一个最长真前缀使得它等于P[0~j-1]的最长真后缀。

 这里最长的意思举个例子如下:虽然下图有2种方法把模式串分成3部分,但是我们取k=3
       

 4. 为什么一定是右移j-k

           但我们在 j 这个位置发现失配的时候(S[i]!=P[j]), 模式串可以右移1,2,..,j-1。那为什么一定是j-k呢。 下图可以帮助理解
     
我们有K={1,4}, 任何不属于K的移动,即右移1,2,3,5,7次,都会有部分模式串与S[....~i-1]不匹配。同时取k=1右移6次的话发现错过了匹配的机会,所以一定是右移j-k次。 具体证明略。

5. 求next

         再说下next[j]=k是把P[0]~P[j-1]分成了3个部分:
        
      但是2这个部分是可以为空的:
    
    如果2为空,1跟1相交也是可以的:
    
注:next[0]= -1, -1 表示S[i]与P[0]不需要比较了,直接比较S[i+1]跟P[0]. 
按照上面的规则:我们来看几个例子:


求next的算法,虽然我们很容易能看出next的值,但是如果从找最长相等的真前缀跟真后缀这个角度出发的话,求出next数组的复杂度可能比朴素匹配的复杂度还高。实际上的求法是一个迭代的过程。即: next[j] = f(next[j-1]), 因为next[0]=-1已知道,f为某种规则。

算法:
          1.  已知next[j]=k, 如果P[j]=P[k], 则next[j+1]=next[j]+1=k+1。原因如下图:


          2. 如果P[j]!=P[k]怎么办呢, 只要一直求一个k(k = next[k]), 使得p[k]=p[j], 或者直到k=-1。如图:
  

上诉过程很容易翻译成代码:
void get_next_tmp(char *p, int next[]){
    int l=strlen(p);
    int i=0,k=-1;
    next[0]=-1;
    while(i<l-1)
    {
        if(k==-1 || p[i]==p[k])
        {
            i++;k++;
            next[i]=k;
        }
        else
            k=next[k];
    }
}

6. 改进next

         上诉的next还可以再改进,能改进的原因其实也很简单。因为我们再求next[j]的时候只考虑了0~j-1串的情况,至于P[j]到底是什么字符完全没有考虑,实际上 j 这个位置的信息能让我们把模式串更加往右移一些。
 
        1. 先看图吧:

这个图的意思是这样的,首先P[j]!=S[i], 按照上述的规则算出来next[j]=k, 有P[k]=p[j]!=S[i], 所以完全没必要把j回溯到k,  我们继续求K' = next[k], 这时候有P[K']=S[i]='a'. 所以方法是:一直求k=next[k], 直到k=-1或者P[K]!=p[j], 实际上我们每一个next[j]都是这样 求出来的,所以只需要一步k=next[k]即可。

用上述的规则把前面的4个例子改下如下:

好了,其实代码也是很直观的:
void get_next(char *P, int n[])
{
    int i=0,k=-1;
    int l=strlen(P);
    n[0]=-1;
    while(i<l-1)
    {
        if(k==-1 || P[i]==P[k]){
            ++i;++k;
            if(P[i]!=P[k])
                n[i]=k;
            else
                n[i]=n[k];
        }
        else
            k=n[k];
    }
}

7. kmp匹配程序

        最后kmp_index函数:
int kmp_index(char *s, char *p)
{
    if(s==NULL || p==NULL) return -1;
    int pl = strlen(p);
    if(pl==0) return -1;

    int *n=(int *)malloc(sizeof(int)*pl);
    get_next(p,n);
    int i=0,j=0;
    while(s[i]!='\0' && p[j]!='\0')
    {
        if(s[i]==p[j])
        {
            i++;j++;
        }else
        {
            if(n[j]!=-1){
                j=n[j];
            }else{
                j=0;
                ++i;
            }
        }
    }
    free(n);
    if(p[j]=='\0')
        return i-j;
    else
        return -1;
}















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值