KMP算法笔记

一、简述

KMP算法是对BF(Brute Force)算法做了很大改进的模式匹配算法,可以将时间复杂度从O(m*n)降低到O(m+n)。

二、KMP算法的思想

设主串s="acabaabaabcacaabc",模式t="abaabcac"

(一)分析BF算法

BF算法匹配过程如下:

第1趟

第2趟

  

第3趟

  

第4趟

第5趟

第6趟

分析BF算法的执行过程,发现造成BF算法慢的主要原因是回溯,在某一趟的匹配过程中,一旦匹配失败,主串要回到本趟开始开始匹配字符的下一个字符,模式串要回到第一个字符,而这些回溯有时候是不必要的。对于第3趟来说,因为s8 != t6,所以才需要进行第4趟的匹配,但是第4趟的匹配是不必要的,因为在第3趟中,有s4 = t2,而t1 != t2,所以,s4 != t1。 同理第5趟匹配也是不必要的,因此可以从第3趟直接到第6趟。进一步分析第6趟,s6和t1的比较是多余的,s7和t2的比较也是多余的,因为第3趟中有,s3~s7=t1~t5,则s6=t4,s7=t5,又因为t1t2=t4t5,必有s6s7=t1t2,因此第6趟的比较可以从s8和t3开始,也就是说,在第3趟的比较中,s8和t6匹配失败后,i指针不动,将模式串向右“滑动”,用t3对准s8继续匹配,依次类推。用这样的处理方法,指针i不需要进行回溯。

综上所述,当si和tj匹配失败后,我们希望指针i不回溯,而是让模式串向右滑动至某个位置k,使得si和tk继续比较。要满足这个假设,则需要满足:

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

等号左边是模式串的前k-1个字符,等号右边是主串si之前的k-1个字符。

又因为k<j,则:

t1t2...tk-1 = tj-k+1tj-k+2...tj-1                                                   (1.1)

结论:

在si和tj匹配失败后,如果在模式串中存在满足式1.1的字串,即模式串的前k-1个字符和模式串tj前的k-1个字符相等时,模式串就可以滑动到tk和si对准继续比较。

(二)next函数

模式串的每一个tj都对应一个k值,由式1.1可知,这个k值只依赖与模式串本身,与主串无关。用函数next(j)来表示tj对应的k值。由以上分析可知,next(j)函数的性质如下:

1)next(j)是一个整数,且0<=next(j)<j;

2)  为了使得t的右移不丢失任何匹配成功的可能,当有多个满足式1.1的k值时,应取最大的,这样向右滑动的距离最短,向右滑动的字符为j-next(j)个;

3)当不存在满足式1.1的字串时,则k=1;即模式串t1和si匹配,此时模式串滑动最远,为j-1个字符。

next函数定义如下:

(三)KMP算法

假设模式串t字符对应的每一个k值都计算好,并且存放在next数组中。指针i和指针j分别指向主串和模式串,依次比较,当si=tj时,指针i和指针j加1,继续匹配;当si != tj时,指针i不变,j=next[j],继续匹配;当j=0时,指针i和指针j加1,即模式串退回至第一个字符,主串移动到下一个字符继续匹配。

 

三、KMP算法代码实现

package DataStructureandAlgorithm;

/**
 * @author
 * @date 2020/1/11 21:07
 */
public class KMP {

    /**
     * @description next[j]值的含义:
     * 1、表示主串与模式串匹配失败后,
     * 模式串应该以索引next[j]字符的值与主串匹配失败的索引的值继续比较
     * 2、表示索引j字符前的最大前缀后缀的后一个字符所在的索引
     * */
    public int[] getNext(String t){
        int k=-1,j=0;
        int len = t.length();
        int[] next = new int[len];
        //模式串的第一个字符next值为-1
        next[0] = -1;
        while(j<len-1){
            if(k == -1 || t.charAt(k) == t.charAt(j)){
                k++;
                j++;
                next[j] = k;
            }else{
                k = next[k];
            }
        }
        return next;
    }

    //返回模式串在主串的索引
    public int getIndex(String s,String t,int next[]){
        int i = 0,j = 0;
        int sLen = s.length();
        int tLen = t.length();
        while(i<sLen && j<tLen){
            if(next[j] == -1 || s.charAt(i) == t.charAt(j)){
                i++;
                j++;
            }else{
                j = next[j];
            }
        }
        if(j == tLen){
            return i-tLen;
        }else{
            return -1;
        }
    }

    public static void main(String[] args){
        KMP kmp = new KMP();
        //s = acabaabaabcacaabc
        //t = abaabcac
        String s = "aabcbabcaabcaabab";
        String t = "abcaababc";
        int next[] = kmp.getNext(t);
        System.out.println(kmp.getIndex(s,t,next));

    }
}

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值