只求自己能看懂的KMP算法

      KMP算法:假设i是指向主串的指针,KMP算法利用已经得到的“部分匹配”的结果将模式串向右“滑动”尽可能远的一段距离后,进行比较,而不需要回溯指针i。

一个简单的例子,反映了KMP算法的匹配过程:

    假设主串为S=s1s2...sn,模式串为T=t1t2...tm(在S中寻找与T匹配的字符串)。

    那么需要解决的问题就是:当匹配过程中产生失配时(si不等于tj),主串中第i个字符(i指针不回溯)应该与模式中哪个字符再比较?

假设此时应与模式中第k个字符比较,那么模式中前k-1个字符应当满足下列关系,且不存在k'>k满足下列关系:

                        t1t2...tk-1 = si-k+1si-k+2...si-1          ①

而已经知道的“部分匹配”的结果是

                       tj-k+1tj-k+2...tj-1 = si-k+1si-k+2...si-1     ②

由上面①②可以得到

                        t1t2...tk-1 = tj-k+1tj-k+2...tj-1               ③

反之,若模式串中存在满足式子③的两个子串,则当匹配过程中,主串的第i个字符与模式中第j个字符比较不等时,仅需将模式右滑至模式中第k个字符和主串的第i个字符对齐。

上面三个式子用图片展示更加明白

     假设用next数组来存储k值,令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应的字符失配时,在模式中需要重新和主串中该字符进行比较的位置。那么

1、当j=1时,next[j]=0,这个0相当于一个标记,如果与模式串的第一个字符失配了即t1与si不等时,那么下次应该比较t1和si+1。

2、当不存在相同的子串时(即找不出b=c),那么next[j]=1,表示下次应该比较t1和si。

3、其他情况next[j]=max{k|1<k<j 且有 b=c}

注意:next数组中的0值是一个标记,表示的是t1与si也不相等,下次需要比较t1和si+1

现在假设我们已经知道了next数组(虽然还不会求)

那么KMP算法的主体就出来了:

//S是主串,T是模式串,pos表示从主串的哪里开始匹配
//注意这里S和T的下标从0开始
//next数组的值也是表示下标,但是它的值的下标是从1开始的,0只是表示一种特殊情况
public static int KMP(String S,String T,int pos){
    int i=pos,j=0;
    next = getNext(T);
    while(i<S.length()){
        //si=tj继续比较si+1和tj+1,或者si与t0不等时继续比较si+1和t0
        if(j==-1||S.charAt(i)==T.charAt(j)){
            i++;j++;
        }
        else{
            j=next[j]-1;//由于next中的值表示的下标是从1开始的,所以要减一
        }
        if(j==T.length())
            return i-T.length();
    }
    return -1;
}

如何求next数组

next[j]=k,一般情况表示当si与tj失配时,下次应该比较si与tk,但如果k=0,则表示那么下次应该比较si+1与t1。

那么可以得到next[1]=0。(注意k的值代表模式串的下标,但是是从1开始的,k=0只是代表特殊情况

假设next[j]=k,求next[j+1]

那么如果tk=tj,明显next[j+1]=k+1=next[j]+1;

如果tk不等于tj,则将求next[j+1]的值看成是字符串匹配的问题,模式串即是主串也是模式串,并且在sj与tk时失配,那么下一步应该将模式串滑动到next[k]。

假设next[k]=k',那么同理如果tk'=tj,next[j+1]=k'+1,否则继续右滑,直到k=0时,则表示不存在这样的k,那么next[j+1]=1,即应该从模式串的第一个开始匹配。

画图解释:

求next数组算法:

//注意这里next数组的下标和字符串的下标都是从0开始
//但是next数组中元素的值代表的下标是从1开始的
public static void getNext(String T){
    next = new int[T.length()];
    int i=0,j=0; //j表示next[i]的值,即k的值
    next[0]=0;  //当与第一个字符失配时,它的next值为0,表示应该比较T[0]和s[i+1]
    while(i<T.length()-1){
        //j=0表示特殊情况,即T[0]的next;由于k表示的下标是从1开始的,所以j-1才是字符的实际位置
        //由于j初值为0,所以这里next[1]一定等于1,
        //当next[1]赋值完之后,j=1,i=1,j正好是next[1]的值,而下一步求的是next[2]
        if(j==0||T.charAt(j-1)==T.charAt(i)){
            i++;j++;
            next[i]=j;
        }
        else{
            j = next[j-1];//next数组的下标是从0开始,k表示的下标从1开始,所以是j-1
        }
    }
}

next数组的修正

假设主串S="aaabaaaab",模式串T="aaaab,使用上面的算法求出的next数组的值应为 0 1 2 3 4

使用kmp算法匹配是在i=4,j=4时失配(下标从1开始),那么之后还会进行i=4、j=3,i=4、j=2,i=4、j=1这三次比较,而实际上模式串中第1~3个字符与第4个字符都相等,因此不需要在和主串的第4个字符比较,而是可以直接比较i=5、j=1。这就是说,若按上述定义得到next[j]=k,而模式中tj=tk,则当tj与si失配时,必定也与tk失配。所以这时不需要与tk比较,而是与t next[k]进行比较。由此可以得到修正算法。

public static void getNext(String T){
    next = new int[T.length()];
    int i=0,j=0; //j表示next[i]的值,即k的值
    next[0]=0;  //当与第一个字符失配时,它的next值为0,表示应该比较T[0]和s[i+1]
    while(i<T.length()-1){

        if(j==0||T.charAt(j-1)==T.charAt(i)){
            i++;j++;
            //若T[i]与T[j-1]相等,则直接比较Tnext[j-1]
            if(T.charAt(i)==T.charAt(j-1))
                next[i] = next[j-1]
            else
                next[i]=j;
        }
        else{
            j = next[j-1];//next数组的下标是从0开始,k表示的下标从1开始,所以是j-1
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值