KMP(模板)

KMP算法

把KMP看成改进的模式匹配算法就够了。

赘述(可不看):

对于串的定位(也叫串的模式匹配),于人而言,把两个串进行逐一比较是最方便和直观的方法,这种方法不依赖其它的串操作;但如果把这种方法用在计算机上,那么就会浪费很多的时间,显然这是不够明智的,所以才有了kmp,记住单纯就KMP模式匹配而言,它并不是很高大上的东西,只是结合了串的其他操作的串的定位算法而已,难点是其实际应用。

 

基本思想:

首先:这里会涉及串的前后缀的应用,前缀指的是一个串不包含最后一个字符,字符串的所有头部子串,如abcd的前缀是a,ab,abc;后缀指的是除第一个字符外,从最后一个字符开始的所有尾部子串,如abcd的后缀是d,cd,bcd。而这种匹配方法,大体上是一个铺路的过程,即后缀为前缀铺路,例如:如果abacfaba的后缀匹配了2个字符,就说明在跳转的时候就可以忽略这两个字符而进行第3个字符的比较,而且这样还实现了准确的跳转到下一个串开始的位置,如果不这样的话,跳转会出错或失去其该有的优点。

其次:记住一个公式

移动位数 = 已匹配的字符数  -  对应的部分匹配值

已匹配的字符数指匹配失败时最大匹配数, 对应的部分匹配值指的是指在这已经匹配的串中,其前缀=后缀的最大值(也就是next数组中的值),由于方便,next[]中值可以后移一位,还可以在原来基础上加一,这里定义看自己喜好。

例子:比如现在有主串ababcabaacbab  需要定位的子串为abcac

对于abcac而言,由其前后缀可得出next数组

于是生成以下表格

对于表格里的内容的应用:

第一次匹配:

a b a b c a b c a c b a b

a b c

由结果知,第一次匹配在第3个字符处失配,此时已经匹配的字符长度为2,接着查刚才得出的next表可知,此时对应的部分匹配值为0,由此可知2-0=2 得出下一次直接从第3个字符开始,跳2个字符,由1->3。(当然字符串是从0开始的,这里从1是为了方便理解)

 

第二次匹配:

a b a b c a b c a c b a b

a b c a c

可知第二次匹配时,在第5个字符处失配(指需要定位的子串的第5个字符,毕竟next是由子串得到的)通过查表可知对应的部分匹配值为1,已经匹配的字符长度为4,所以4-1=3将子串后移动3个位置,从3到6。对了这里的 对应的部分匹配值是看失配字符的前一个字符的值,即4a的对应的部分匹配值为0, 5c的对应的部分匹配值才为1。其实很好理解,当前位置失配表示当前的字符并不能为后续的操作提供有利的条件,所以选上一个成功匹配的字符。

 

第3次匹配:

a b a b c a b c a c b a b

a b c a c

 

第3次成功匹配,所以子串定位为6。而kmp总体复杂度为o(m+n)。.

 

对于next的求法,记住next[j]=next[j-1]+1 这个式子 -->如果有字符匹配时 j 保存的最长前后缀与j-1次保存的有关

 

Next[j]已知 求next[j+1]两步:

1 若串中字符tj =ti ,则next[i+1]=j+1 ,j为当前最长相等前后缀长度(不是全局)

2若tj != ti  将 ti-j+1........ti作为主串,t1......tj作为子串,类比于失配让j=next[j] 继续比较,若满足1则求得next[j+1]。 如abcdcd  串中每次前缀都是从a开始的,所以只要每次不断失配后j能跳到a,则表明回跳是对的,后缀一样。

//获取next数组
//记住当不匹配的时候 我们要找的是上一次完成匹配的情况下的最大匹配值
//目的: 这样就不用把i回溯来匹配了,所以next的存储方式就出来了

void getnext(char *ptr){
    int i,n,k;   //个人习惯用k,k可改回j 方便理解
    n=strlen(ptr);
    i=1,k=0; //这里已经实行了右移 再加1    如不加1 i=0, k=-1,next[0]=-1
    next[1]=0;//

    while(i<n)
    {
        if(k==0 || ptr[i]==ptr[k])// k=0用于指针i的移动 结合k=next[k]看
        {
           i++;
           k++;
           next[i]=k;
        }
        else k=next[k];
    }
}

为了直观一点,我写一下关于串的next的模拟:

如求一个串 ababaa的next

首先默认 next[1]=0  ,next[2]=1    因为移位后是-1  0   再加1 就是0 1    -1是移位后添加的,0是第一个字符没有前后缀

之后当 i=3 时 k=next[i-1] =next[2]=1   此时比较 str[k]与  str[i-1]   即比较 str[1] 与str[2] 明显不相等——> 由k=next[k]

知k=0 ,所以不用比较 ,next[3]=k+1=1

当 i=4 时 k=next[i-1]=next[3]=1   由此比较str[1] 与str[3]  可知 str[1] =a   str[3]=a  str[1]=str[3] ,  此时next[4]=k+1 =2

当 i=5 时 k=next[i-1]=next[4]=2   由此比较str[2] 与str[4]  可知 str[2] =b   str[4]=b  str[2]=str[4] ,  此时next[5]=2+1 =3

当 i=6 时 k=next[i-1]=next[5]=3   由此比较str[3] 与str[5]  可知 str[3] =a   str[5]=a  str[3]=str[5] ,  此时next[6]=3+1 =4

所以结果为011234  这是移位并且加1 后的结果

为什么要移位再加1?   因为move =已匹配的字符数 - 对应部分的匹配值 

                                            move= ( j-1 ) -next[j-1] 

而移位的位置标识j为 j = j-move ,这是字符串的移动公式结合kmp的 j 看 。 

                                            j-move=j - ((j-1) -next[ j ] )=next[j]+1    注意这个next[j]是移位后的next  原来是next[ j-1] 。

 综上,再将next 数组加1 就可以得到next[ j ]    然后得到 j= next[ j ] (KMP中的)

应该看得懂吧

 

结合了《算法导论》和《数据结构》的思想,而在数据结构中。数据结构中会把str[0]用于存放串长度。

int kmp(char a[],char b[])//匹配ab两串,a为父串,b为带定位串
{
    int i=-1,j=-1;
    int len1=strlen(a);
    int len2=strlen(b);
    getnext(b);
    while(i<len1&&j<len2)
    {
      if(j==-1||a[i]==b[j])
        ++i,++j;      //继续比较后续字符
      else
        j=next[j];    //待定位串向右移动
    }
    if(j==len2)
    return  i-j;// 匹配成功
    else
        return -1;
}

 

    ABCDABTABCDABC
    ABCDABC
 

把上面的样例放入程序动手模拟一下就可以明白大概了

 

这里next数组的作用就显现出来了。最后返回的是i-j,也就是说,是从i位置前面的第j位开始的,也就是上面说的,next数组也可以说是开始比较的位数。也就是说,在父串的i位比的时候已经是在比子串的第j位了。(看不懂也没关系)

一个完整的代码://懒癌犯了 细节以后再补

#include <iostream>
#include <cstring>
#include <string>
using namespace std;
int next[50]={-1};
void getnext(char *ptr){
    int i,n,k;   //个人习惯用k,k可改回j 方便理解
    n=strlen(ptr);
    i=1,k=0; //这里已经实行了右移 再加1    如不加1 i=0, k=-1,next[0]=-1
    next[1]=0;//

    while(i<n)
    {
        if(k==0 || ptr[i]==ptr[k])// k=0用于指针i的移动 结合k=next[k]看
        {
           i++;
           k++;
           next[i]=k;
        }
        else k=next[k];
    }
}

int kmp(char a[],char b[])//匹配ab两串,a为父串,b为带定位串
{
    int i=-1,j=-1;
    int len1=strlen(a);
    int len2=strlen(b);
    getnext(b);
    while(i<len1&&j<len2)
    {
      if(j==-1||a[i]==b[j])
        ++i,++j;      //继续比较后续字符
      else
        j=next[j];    //待定位串向右移动
    }
    if(j==len2)
    return  i-j;// 匹配成功
    else
        return -1;
}
int main()
{
    char a[20],b[20];
    cin>>a;
    cin>>b;
   int num= kmp(a,b);
   if(num)cout<<"NOT FIND!"<<endl;
   else cout<<num<<endl;

return 0;
}
借鉴:https://www.cnblogs.com/yjiyjige/p/3263858.html

不过这东西,真的在题中出现时用的是求next等思想。

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值