ACM Note No.21: KMP


ACM Note No.21: KMP

KMP 是一种用来匹配最长相等的真前缀串和真后缀串的方法。

我们首先定义失配数组(又叫前缀数组)fail[i] 为字符串 s 的子串 s[0 ... i] 的最长相等的真前缀串和真后缀串的长度

如何计算这个数组,我们可以注意到两个优化:

第一个优化,我们注意到对于当前的检查的下标 ipi[i] 至多比 pi[i - 1] 大一,因此我们可以考虑通过分类讨论从 pi[i - 1] 转移。我们令 j 为前缀串最后一个字符的下一个字符,i 为后缀串最后一个字符的下一个字符,如果 s[i] == s[j],我们就可以直接从 pi[i - 1] 转移到 pi[i - 1] + 1

当然有不能直接转移到情况,这时候我们就要用到第二个优化。

第二个优化,对于不能转移的情况,就需要找到次长相等前后缀串。我们先将注意力放到下标 i - 1 的最长相等前后缀串的前缀串。我们注意到这个串的最长相等前后缀串的后缀串,实际上就是下标 i - 1 的最长相等前后缀串的后缀串的次长相等前后缀串的后缀串。

因此我们可以在这个性质上进行状态转移,也就是在 pi[] 上进行动态规划。

通过这两个优化,我们就能将 pi 数组的计算优化到 O(n)

我们也可以通过这个计算方法,在一个字符串中查找另一个字符串的子串。

例如查找的子串为 S ,被查找的串为 T 那么我们可以构造 S#T 其中 # 为在两个串中均未出现的字符。

然后计算 S#Tpi 数组,如果 pi[i] == S.length() 则成功匹配子串。

#include <bits/stdc++.h>
using namespace std;

template<class T>
struct KMP {
    const T s;
    int n;
    vector<int> pi;

    KMP(const T &s) : s(s), n((int)s.size()), pi(n) {
        for (int i = 1; i < n; i++) {
            int j = pi[i - 1];
          	// 不满足第一个优化条件的情况:s[i] != s[j]
        		// DP, 如果不满足第一个优化的条件就使用第二个优化条件进行状态转移
            while (j > 0 && s[i] != s[j]) j = pi[j - 1];
          	// 第一个优化条件:s[i] == s[j] 
        		// 状态转移结束后检查第一个优化条件
            if (s[i] == s[j]) j++;
            pi[i] = j;
        }
    }

    vector<int> match(const T &t) {
        int m = (int)t.size();
        vector<int> res;
        int j = 0;
        for (int i = 0; i < m; i++) {
            while (j > 0 && t[i] != s[j]) j = pi[j - 1];
            if (t[i] == s[j]) j++;
            if (j == n) {
                res.push_back(i - n + 1);
                j = pi[j - 1];
            }
        }
        return res;
    }
};

声明:Blog|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - ACM Note No.21: KMP