KMP算法(基础)

254 阅读3分钟

KMP算法使用环境

当我们进行字符串匹配的时候,暴力匹配是最容易想到的方法。但是由于暴力匹配需要将模式串的每一位都和主串进行匹配,所以时间复杂度高达O(mn)。其中m为主串长度,n为模式串的长度。在主串与模式串长度都比较大时,暴力匹配的性能会变得很差。这个时候需要使用新的方法来进行字符串匹配,KMP算法应运而生。

KMP算法基本原理

KMP算法主要是消除无用的匹配,设主串为ABBABAABABBAA,模式串为BABBA。引入三个概念,最大前缀、最大后缀和最大前缀后缀公共元素长度。例如:模式串BABBA的最大前缀是BABB,最大后缀是ABBA,最大前缀后缀公共元素的计算是前缀从前往后,后缀从后往前。例子中的最大前缀后缀公共元素是BA,长度为2。

匹配过程

当模式串与主串不匹配的时候,需要重新定位匹配的位置。如下图,如果是暴力匹配的话则需要把模式串右移一位,从模式串第一位和主串第四位进行匹配。而KMP存在前缀后缀公共元素。可以把前缀移动到后缀所在的位置,而后从前缀的后一位开始匹配,即模式串的第二位与主串的第六位进行匹配。这种匹配方式在匹配过程中只需要匹配整个主串即可。所以时间复杂度是O(m),但这个并不是这个算法的整体复杂度。这个算法需要生成一个数组,长度为模式串长度。

next数组

用来生成next数组的表格

模式串字串 最大前缀 最大后缀 最大前缀后缀公共元素 最大前缀后缀公共元素长度
B 0
BA B A 0
BAB BA AB B 1
BABB BAB ABB B 1
BABBA BABB ABBA BA 2

最大前缀后缀公共元素长度数组

B A B B A
0 1 2 3 4
0 0 1 1 2

next数组

B A B B A
0 1 2 3 4
-1 0 0 1 1
private final static int ZERO = 0;
private final static int ONE = 1;
private final static int DEFAULT = -1;
public static void main(String[] args) {
    String mainStr = "ABBABAABABBAA";
    String pattenStr = "BABBA";
    KmpStandard kmpStandard = new KmpStandard();
    int ans = kmpStandard.kpmCompare(mainStr,pattenStr);
    if(ans == -1){
        System.out.println("没有匹配到字符串");
    }else{
        System.out.println("从第" + ans + "位匹配到字符串");
    }
}

使用next数组进行字符串匹配

public int kpmCompare(String mainStr,String pattenStr){
    byte[] bMain = mainStr.getBytes();
    byte[] bPatten = pattenStr.getBytes();
    int length = bPatten.length;
    int mainLength = bMain.length;
    //构建数组
    int[] temp = new int[length];
    int[] next = new int[length];
    //默认第一位为0
    next[ZERO] = ZERO;
    //设置指针
    int jndex = ZERO;
    int index = ONE;
    //数组赋值
    for(;index < length;index++){
        if(bPatten[index] == bPatten[jndex]){
            temp[index] = ++jndex;
        }else if(jndex == ZERO){
            temp[index] = ZERO;
        }else{
            while(jndex > ZERO) {
                jndex = temp[jndex - 1];
                if (bPatten[jndex] == bPatten[index]) {
                    temp[index] = ++jndex;
                    break;
                }
            }
            if(jndex == ZERO) {
                temp[index] = ZERO;
            }
        }
    }
    //生成next数组
    int t = ONE;
    next[ZERO] = DEFAULT;
    for(;t < length;t++){
        next[t] = temp[t-ONE];
    }
    //进行比较
    int patten = ZERO;
    for(int match = ZERO;match < mainLength;){
        if(bMain[match] == bPatten[patten]){
            match++;
            patten++;
        }else if(next[patten] == DEFAULT){
            match++;
        }else{
            patten = next[patten];
        }
        //判断是否匹配到出口
        if(patten == length){
            return match - patten + ONE;
        }
    }
    return DEFAULT;
}

使用最大前缀后缀公共元素长度数组进行匹配

public int kpmCompare(String mainStr,String pattenStr){
    byte[] bMain = mainStr.getBytes();
    byte[] bPatten = pattenStr.getBytes();
    int length = bPatten.length;
    int mainLength = bMain.length;
    //构建数组
    int[] next = new int[length];
    //默认第一位为0
    next[ZERO] = ZERO;
    //设置指针
    int jndex = ZERO;
    int index = ONE;
    //数组赋值
    for(;index < length;index++){
        if(bPatten[index] == bPatten[jndex]){
            next[index] = ++jndex;
        }else if(jndex == ZERO){
            next[index] = ZERO;
        }else{
            while(jndex > ZERO) {
                jndex = next[jndex - 1];
                if (bPatten[jndex] == bPatten[index]) {
                    next[index] = ++jndex;
                    break;
                }
            }
            if(jndex == ZERO) {
                next[index] = ZERO;
            }
        }
    }
    //进行比较
    int patten = ZERO;
    for(int match = ZERO;match < mainLength;){
        if(bMain[match] == bPatten[patten]){
            match++;
            patten++;
        }else if(patten == ZERO){
            match++;
        }else{
            patten = next[patten];
        }
        //判断是否匹配到出口
        if(patten == length){
            return match - patten + ONE;
        }
    }
    return DEFAULT;
}

记录下学习历程,实时补充