Bootstrap

KMP算法-时间复杂度分析

KMP算法

假设m为模式串strM的长度,n为待匹配的字符串strN的长度。

KMP的基本过程
  1. 求模式串strM的next数组
  2. 遍历比较待匹配的字符串strN(过程=遍历strN+遍历时出现strM[j]的回跳)
    比较strN[i]、strM[j]时可能出现的情况为:
    2.1 当前字符匹配时,同时移动 i++,j++
    2.2 当前字符不匹配,且j=0时,只移动 i++,j=0不动
    2.3 当前字符不匹配,且j!=0时,i不变,strM[j]回跳,当前strN[i]时最多回跳j次

基本的过程参考下面博文就可以:
https://blog.csdn.net/sinat_37537673/article/details/73331135 (忽略里面的代码部分)
https://blog.csdn.net/christ1750/article/details/51259425
https://www.cnblogs.com/imzhr/p/9613963.html

KMP的next数组原理

假设模式串为:strM[11] = ABCDABCDABE
对应的next数组为: next[11] = 0,0,0,0,1,2,3,4,5,6,0

j012345678910
strM[j]ABCDABCDABE
next[j]00001234560

例如到strN[i] -> H, strM[j=10] -> E不匹配,可能的回跳为:
第一次最大匹配回跳:next[10-1]=6 -> ABCDAB*
如果第一次回跳后strN[i], strM[j=6] 不match,第二次最大匹配回跳:next[6-1]=2 – AB*
如果第二次回跳后strN[i], strM[j=2] 不match,第三次最大匹配回跳:next[2-1]=0 – *

所以实际上该过程可以抽象为:
当前比较strN[i],strM[j];

如果strN[i],strM[j] 匹配时, i++,j++;
如果strN[i],strM[j] 不匹配,且j=0时, i++;

如果strN[i],strM[j] 不匹配,且j!=0时,基于strM[0-j]串,寻找前一个最大匹配串strM[0-k],k<j,赋值j=k+1,看strN[i]和strM[j=k+1]是否匹配了;
如果还是不匹配且j!=0,再基于strM[0-j]串,寻找前一个最大匹配串strM[0-h],h<(j=k),赋值j=h+1,看strN[i]和strM[j=h+1]是否匹配了;

例如ABCDABCDAB*(*号为当前比较的字符), 后缀ABCDABCDAB*就是ABCDABCDAB*里的前缀包含的关系,那就可以再以ABCDAB*(*号为当前比较的字符,当前模式串里就是’C’)为基础,再进行比较…

next数组的获取实现

可以直接看代码的实现

KMP的时间复杂度分析

假设m为模式串strM的长度,n为待匹配的字符串strN的长度。

O(m+n)=O( [m,2m]+ [n,2n] ) = 计算next数组的时间复杂度+遍历比较的复杂度。

也就是:
计算next数组时的比较次数介于[m,2m]。
遍历比较的比较次数介于[n,2n],最坏情形形如T=“aaaabaaaab”,P=“aaaaa”。
所以算法时间复杂度时O(m+n).

这里分析下[n,2n]的最坏情况是怎么得出的,可以抽象下这样理解,遍历待匹配字符串strN时,比较strN[i]、strM[j]时可能的情况为:
1.当前字符匹配时,同时移动 i++,j++
2.当前字符不匹配,且j=0时,只移动 i++,j=0不动
3.当前字符不匹配,且j!=0时,i不变,strM[j]回跳,最多跳j次,但j由前面匹配的情况1确定,而情况1总共不可能出现超过n次,所以总回跳不会超过n次
所以最坏情况,i++次数(情况一+情况二)+ j回跳(情况3)= n + 最坏n = 2n

[m,2m]也可以类似证明。
这里也可以换个更简单的方式和思路去证明。

参考文档:
KMP时间复杂度分析

KMP的Java实现

java代码实现为:

package classic;

public class KMP {

    public static void main(String[] args){
        KMP kmp=new KMP();
        System.out.println(kmp.kmp("BBCWABCDABWABCDABCDABDE","ABCDABD"));
    }

    private int kmp(String originStr, String subString) {
        if (originStr == null || subString == null || originStr.length() == 0 || subString.length() == 0
                || originStr.length() < subString.length()) {
            return -1;

        }
        int[] next = getNext(subString);
        for(int i = 0,j=0; i < originStr.length(); i++){
            while(j > 0 && originStr.charAt(i) != subString.charAt(j)){
                j = next[j - 1];
            }
            if(originStr.charAt(i) == subString.charAt(j)){
                j++;
            }
            if(j == subString.length()){
                return i-j+1;
            }
        }
        return -1;
    }

    private int[] getNext(String str) {
        int[] next = new int[str.length()];
        next[0] = 0;
        int j;
        for(int i = 1; i < str.length(); i++){
            j=next[i-1];
            while(j > 0 && str.charAt(j) != str.charAt(i)){
                j = next[j - 1];
            }
            if(str.charAt(i) == str.charAt(j)){
                next[i]=j+1;
            }
            else
                next[i]=0;
        }
        return next;
    }
}

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;