Algorithm - KMP 字符串匹配算法

前言

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。

以上是百度百科对 KMP 的描述,在开始介绍 KMP 算法时我们先想一下如何对一字符串找到和它是否匹配的字符串。

BF 算法

暴风(Brute Force)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。时间复杂度为 O (m * n)。

代码实现:

int bfMarch(String text, String pattern) {
    int index = -1;
    int i = 0;
    int j = 0;
    int k = 0;
    while (i < text.length() && j < pattern.length()) {
        if (text.charAt(i) == pattern.charAt(j)) {
            i++;
            j++;
        } else {
            j = 0;
            i = ++k;
        }
    }
    if (j == pattern.length()) {
        index = i - pattern.length();
    }
    return index;
}

以上代码等同于

int bfMarch(String text, String pattern) {
    int index = -1;
    for (int i = 0; i < text.length(); i++) {
        for (int j = 0; j < pattern.length(); j++) {
            if (text.charAt(i) != pattern.charAt(j)) {
                break;
            }
            if (j == pattern.length() {
                return i - pattern.length();
            }
        }
    }
    return index;
}

KMP 算法

BF 算法比较简单,因为符合我们的逻辑思维,时间复杂度接受不了,在某种意义上为 O (m^2)。接下来我们看看另一种算法,它的时间复杂度为 O (n + m),空间复杂度为 O(m)。刚开始学习这个算法时,在网上找的资料我看的是蒙*的,说的太高深了,直到我在 B 站看了一个大神的视频,资源里有链接。我非常推荐大家先去看视频,比我描述的好多了。

pattern 字符串是和 text 字符串匹配的字符串,pattern.length() <= text.length()。

KMP 的主要思想是创建一容量和 pattern 字符串一样的整型数组,里面存储与前缀相同字符的位置,如:

abcdabaa  // pattern
01234567  // 数组下标
00001211  // 数组存储的值

值如何求?使用两个指针 i、j,arr[0] = 0,i 指向 arr[0],j 指向 arr[1],当判断 pattern.charAt(0) == pattern.charAt(1) 时,arr[1] = i + 1,即 0 + 1,如上列的第 3 和第 4 位,之后 i++ 和 j++。当不匹配时,则向前移一位,i 被赋值为 arr[i - 1],即 i = arr[i - 1],直到 i 变为零,如果还不匹配,则 j++。最后,j > pattern.length() 时结束。
数组的值表示,当 pattern 和 text 对比不匹配时,向前移动一位,pattern 移动的到该值表示的位置。

代码:

/**
 * 当字符不匹配时,可根据下标返回对应位置。
 * 目的:减少匹配次数
 *
 * @param patternStr
 * @return
 */
private static int[] computeTemporaryArray(String patternStr) {
    char[] pattern = patternStr.toCharArray();
    int[] lps = new int[pattern.length];
    int index = 0;
    for (int i = 1; i < pattern.length; ) {
        if (pattern[i] == pattern[index]) {
            lps[i] = index + 1;
            index++;
            i++;
        } else {
            if (index != 0) {
                index = lps[index - 1];
            } else {
                lps[i] = 0;
                i++;
            }
        }
    }
    return lps;
}

然后就开始匹配两字符串了,代码如下:

/**
 * 找到 pattern 在 text 第一次出现的位置
 *
 * @param textStr
 * @param patternStr
 * @return pattern 第一个字符的位置
 * -1 表示不匹配
 */
private static int indexOfStr(String textStr, String patternStr) {
    int index = -1;
    char[] text = textStr.toCharArray();
    char[] pattern = patternStr.toCharArray();
    // KMP
    int lps[] = computeTemporaryArray(patternStr);
    int i = 0;
    int j = 0;
    while (i < text.length && j < pattern.length) {
        if (text[i] == pattern[j]) {
            i++;
            j++;
        } else {
            if (j != 0) {
                j = lps[j - 1];
            } else {
                i++;
            }
        }
    }
    if (j == pattern.length) {
        index = i - pattern.length;
    }
    return index;
}

完整代码

这里还有另一种写法

public class KMP {
    public static int KMPSearch(String txt, String pat, int[] next) {
        int M = txt.length();
        int N = pat.length();
        int i = 0;
        int j = 0;
        while (i < M && j < N) {
            if (j == -1 || txt.charAt(i) == pat.charAt(j)) {
                i++;
                j++;
            } else {
                j = next[j];
            }
        }
        if (j == N)
            return i - j;
        else
            return -1;
    }
    public static void getNext(String pat, int[] next) {
        int N = pat.length();
        next[0] = -1;
        int k = -1;
        int j = 0;
        while (j < N - 1) {
            if (k == -1 || pat.charAt(j) == pat.charAt(k)) {
                ++k;
                ++j;
                next[j] = k;
            } else
                k = next[k];
        }
    }
    public static void main(String[] args) {
        String txt = "BBC ABCDAB CDABABCDABCDABDE";
        String pat = "ABCDABD";
        int[] next = new int[pat.length()];
        getNext(pat, next);
        System.out.println(KMPSearch(txt, pat, next));
    }
}

资源

【soso字幕】汪都能听懂的KMP字符串匹配算法【双语字幕】

你可能感兴趣的:(算法,kmp,字符串,Algorithm)