KMP算法的java实现

前言

KMP算法看了几篇博文还是比较懵逼,主要是这个算法比较难表述清楚。阮一峰的博客 还有一篇CSDN大佬写的,这两篇感觉是写的比较清楚的,超链接已经加上,大家可以去看看,我也没必要重复写了。
我感觉最最重要的是理解清楚各个概念,然后再去看代码,这样会简单一些。理一下KMP比较重要的几个概念:

  1. 什么是前后缀?
  2. 如何根据前后缀求部分匹配值?
  3. 求得部分匹配值后怎么求next移动数组?

    这三个问题搞明白应该就能搞懂KMP了。

Take is cheap, show me the code!

当时用java完成的原因是因为觉得可能会用一些比较高级的字符串函数,最终也没用到什么,就求串长和取子串,所以大家也可以用C/C++实现一下,不是很复杂。

编码

就以String str = "BBC ABCDAB ABCDABCDABDE"String pattern = "ABCDABD" 为例。

计算移动步数

首先计算部分匹配值,将pattern字符串进行拆分,计算从头开始的7个子串的部分匹配值部分匹配值指的是字符串最长相同前后缀的长度。前后缀之类的定义建议先去看一下之前的两篇博客,这里不加以赘述了。

为什么要从头开始拆分7个子串呢?
原因在于,7个子串分别对应了在第几个位置出现了不匹配,然后我们可以根据pattern前后缀匹配值进行移动,这就是网上所谓利用了pattern自身的性质。
就好比ABCDABCDA,pattern是ABCDABD,我开头肯定是以A开头的进行匹配的,那么我比完第一个A之后其实可以直接移动4位到下一个A进行匹配。

大概说明一下:
1. 第一个从头开始的子串A,没有相同前后缀,赋值为0
2. 第二个AB,没有相同前后缀,赋值为0
3. 第三个ABC,没有相同前后缀,赋值为0
4. 第四个ABCD,没有相同前后缀,赋值为0
5. 第五个ABCDA,最长相同前后缀A,赋值为1
6. 第六个ABCDAB,最长相同前后缀AB,赋值为2
7. 第七个ABCDABD,没有相同前后缀,赋值为0
KMP算法的java实现_第1张图片
然后使用这个公式:

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

那我们就知道:
1. 第一个从头开始的子串A,长度为1,移动位数1-0 = 1
2. 第二个AB,长度为2,移动位数2-0 = 2
3. 第三个ABC,长度为3,移动位数3-0 = 3
4. 第四个ABCD,长度为4,移动位数4-0 = 4
5. 第五个ABCDA,长度为5,移动位数5-1 = 4
6. 第六个ABCDAB,长度为6,移动位数6-2 = 4
7. 第七个ABCDABD,长度为7,移动位数7-0 = 7

获取pattern移动数组代码,因为懒得new对象了,就写了static,如下:

/**
     * movelen = arrlen - maxmatchlen
     * @param pattern 匹配字符串
     * @return pattern对应的移动数组
     */
    static int[] getMoveArr(String pattern){
        //初始化moveArr数组并赋初值为0
        int[] moveArr = new int[pattern.length()];
        Arrays.fill(moveArr, 0);

        //将pattern所有子串提出
        for(int i = 0; i < pattern.length(); i++){
            String substr = pattern.substring(0, i+1);
//          System.out.println(i + substr + ":");
            //比较子串前缀、后缀是否相同,从长度为1开始
            for(int j = 0; j < substr.length()-1; j++){
                String prefixStr = substr.substring(0, j+1);
                String suffixStr = substr.substring(substr.length()-1-j, substr.length());
                //如果相同将部分匹配值赋给moveArr
                if(prefixStr.equals(suffixStr)){
//                  System.out.println(headStr + "-" + tailStr);
                    moveArr[i] = prefixStr.length();
                }
            }
            //利用movelen = arrlen - maxmatchlen公式
            moveArr[i] = substr.length() - moveArr[i];
        }

        return moveArr;
    }

获取第一次匹配的索引

其实接下来就很简单了,从头开始匹配patten,每次提取出长度为7(pattern.length())的子串与pattern进行匹配。匹配成功,返回索引;匹配失败,找到第一次不匹配的位置,根据上面的移动数组进行移动,减少匹配次数。最后如果没找到,返回-1。

static int indexKMP(String str, String pattern){
        //获取当移动数组
        int[] moveArr = getMoveArr(pattern);

        //从开始遍历str
        for(int strIndex = 0; strIndex+pattern.length() <= str.length(); ){
            //从str中提取出pattern长度的字符串
            String subStr = str.substring(strIndex, strIndex+pattern.length());
            //匹配成功,返回结果
            if(subStr.equals(pattern)){
                return strIndex;
            } else {
                //匹配失败,找到第一次失败子串的长度,strIndex使用移动数组移动
                for(int j = 0; j < pattern.length(); j++){
                    String strSub = subStr.substring(0, j+1);
                    String patternSub = pattern.substring(0, j+1);
                    //此处在该循环中必然会成立一次,因此for循环自增不需要
                    if(!strSub.equals(patternSub)){
                        strIndex += moveArr[j];
//                      strIndex--;//均衡for循环自增
                        break;
                    }
                } 
            }
        }
        //失败返回-1
        return -1;
    }

主函数:

    public static void main(String[] args) {
        String str = "BBC ABCDAB ABCDABCDABDE";
        String pattern = "ABCDABD";
        System.out.println(indexKMP(str, pattern));
    }

你可能感兴趣的:(算法)