关于KMP算法——模式串在文本串中是否出现过

57 阅读3分钟

1.KMP算法介绍

  • KMP本身无特殊的英文含义,是由Donald Kunth、Vaughan Pratt、James H.Morris三位科学家发表。
  • 在数据结构与算法中,是判断模式串在文本串中是否出现过/最早出现位置的经典算法。
  • 其核心思想是:利用前缀表(prefix table)用来回退(而不是回退到起始位置),前缀表中记录了当模式串与文本串不匹配时,模式串应该退回哪个位置重新匹配。
  • 通常是构建next数组作为前缀表,该数组中的元素代表字符串中当前字串中最长相等前后字串的长度。
  • 如果假设文本串的长度为m,模式串为n,那么暴力解法即两层for循环,退回到起始位置,算法的时间复杂度为O(m×n),使用KMP算法则将时间复杂度降为O(m+n),大大减少了计算时间。

2.最长相等前后缀

  • 字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

    以"aabbaa"为例

    • 前缀:a, aa, aab, aabb, aabba
    • 后缀:abbaa, bbaa, baa, aa, a
  • 我们可以发现,相等的前后缀有aaa,最长|相等的|前后缀即为aa,长度为2。(有人没理解可能是断句没断好)

3.前缀表(next数组)

  • 在数据结构与算法中,前缀表通常以int next [len] 这样一个数组进行表示。其中len代表的是模式串的长度,next [i] 表示前i个字符构成的字串即str [0: i] 中最长相等前后缀的长度。由于第一个字符str[0]没有前后缀,因此next [0] = 0。

    以模式串 str = "aabbaaf" 为例

    • str[0: 1] = "aa",最长相等前后缀长度为1,因此 next [1] = 1;
    • str[0: 2] = "aab",最长最长相等前后缀长度为0,因此 next [2] = 1,以此类推。

image.png

4.当字符不匹配时,指针应移动的位置

  • 从头开始遍历文本串"aabbaabbaaf",当匹配到文本串与模式串不相等的字符时,则回退到之前已经匹配好的字符的位置。

    以模式串"aabbaaf",文本串"aabbaabbaaf"为例

image.png

  • 可以发现,回退的位置刚好为next [i-1]

5.C++代码实现

  • 时间复杂度:O(n + m)
  • 空间复杂度:O(m)
class Solution {
public:
    // next数组初始化
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0; 
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) { 
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        vector<int> next(needle.size());
        getNext(&next[0], needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) { // 匹配到不相等,回退到已经匹配好的位置
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) { // 匹配到相等字符,往前移动
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};