普通的周日 雨天 初遇KMP算法

星期天,雨加雪,出门丢三落四,给我的没耐心做了铺垫。


学习了KMP算法,是用来做字符串匹配的:在主串中找到与模式串相同的子串,并返回其位置。

先来说一下朴素模式算法

像这样,如果第一位不匹配,指向主串的指针i++,拿着下一个长度为6的子串与模式串继续匹配,依次类推......

假如主串长度为n,模式串长度为m

将主串中所有长度为m的子串依次与模式串比较,直到找到一个完全匹配的子串,或者所有子串都不匹配,长度为n的主串,有多少个长度为m的子串?--- n-m+1个,所以最多比较(n-m+1)次

大概是这个样子:

但是我们会发现一个问题:

比如主串是“g o o g l m”,子串是“g o o g l e”,当我们发现第六位不匹配时,朴素算法就会这样去做了:

但是我们肯定知道,第二位的字符o和g肯定是不匹配的呀,包括后面,这种情况就没有必要再去匹配了,结果肯定失败

第六位字符匹配失败,只能说明主串的第六个字符一定不是e,但我们可以期待一下,他是否为g呢?

像这样:

如果我们这样想,把指向模式串的指针 j 往后走5步,相当于模式串右移5步,效率就大大提高了

总结:当某些子串与模式串能部分匹配时,主串的指针 i 回溯次数太多,导致效率变低

有没有什么办法,让主串的指针 i 不回溯,使模式串的指针 j 回溯,(相当于模式串右移),由此引出了KMP算法


KMP算法

        KMP算法利用的是已匹配部分中的前缀 后缀 来进行下一次的匹配,如果匹配不成功,会回到匹配过的 前缀的下一位置开始,而不是从头重新开始,那样多多少少有点暴力~~

这里举了几个例子:

 假如在第k位匹配失败,那么前k-1位则是匹配成功的,我们把前1~(k-1)个字符组成的串记作S

 那么 j = S的最长的相等前后缀长度+1

然后把 j 的移动记录到一个next数组(前缀表)中

然后我就去试了一下伪代码:

//求模式串T的next数组
void getNext(String T,int next[]){

    char[] arr = T.toCharArray();
    
        int i=1,j=0;
        next[1] = 0;

    while(i<T.length()){
        if(j==0 || arr[i]==arr[j]){
            ++i;++j;
            next[i] = j;
    }
    else{
        j = next[j];
        }
}

}
//KMP算法
int KMP(String S,String T){
       
        int i=1;
        int j=1;
        int [] next = new int[T.length()+1];

        char[] arrS = S.toCharArray();
        char[] arrT = T.toCharArray();

        getNext(T,next);
        
        while(i<=S.length() && j<=T.length()){

            if(j==0 || arrS[i]==arrT[j]){
                    ++i;
                    ++j;

            }else{
                    j = next[j];
            }
   
        }

          if(j>T.length()){
            return i-T.length();//匹配成功
          }
            else{
                return 0;
}
}

此时我的心情非常美妙,大体知道KMP是干啥的了,但是我意识到这个伪代码有问题

于是就去搜KMP源代码,搜了好多版本,一个看不下去就换下一个,看了好多.....然后逐渐没有耐心了┭┮﹏┭┮

努力地研究了一个,代数试了一下,看懂了.... 所以真的不要浮躁,摁住一个老老实实看

但是,今天初识KMP,只是看懂了别人的代码,属于“消费者”,下一次再研究时,就要变成“创作者”

没错,我很菜,但也没那么脆弱;今天搞得不是很清楚,但是,KMP,我不会放过你的~

ps这是源代码,正确的:




public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        //String str2 = "BBC";
        int[] next = kmpNext("ABCDABD"); //[0, 1, 2, 0]
        System.out.println("next=" + Arrays.toString(next));
        int index = kmpSearch(str1, str2, next);
        System.out.println("index=" + index); // 15 了

    }

    /**
     * @param str1 源字符串
     * @param str2 子串
     * @param next 
     * @return 如果是-1 就是没有匹配到,否则返回第一个匹配的位置
     */
    public static int kmpSearch(String str1, String str2, int[] next) {
        //遍历
        for (int i = 0, j = 0; i < str1.length(); i++) {
            //需要处理 str1.charAt(i) != str2.charAt(j), 去调整 j 的大小
            //KMP 算法核心点, 可以验证...
            while (j > 0 && str1.charAt(i) != str2.charAt(j)) {
                j = next[j - 1];
            }

            if (str1.charAt(i) == str2.charAt(j)) {
                j++;
            }
            if (j == str2.length()) {//找到了 // j = 3 i
                return i - j + 1;
            }
        }
        return -1;//没找到
    }

    //获取到一个字符串(子串) 的部分匹配值表
    public static int[] kmpNext(String dest) {
       
        int[] next = new int[dest.length()];
        next[0] = 0; //如果字符串是长度为 1 部分匹配值就是 0
        
        for (int i = 1, j = 0; i < dest.length(); i++) {
           
            while (j > 0 && dest.charAt(i) != dest.charAt(j)) {
                j = next[j - 1];
            }

           
            if(dest.charAt(i) == dest.charAt(j)) {
                j++;
            }
            next[i] = j;
        }
        return next;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值