【知识点】KMP算法
定义
KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,由三位大佬1977年联合提出,它能解决在一个字符串中找到另一个字符串的功能。
待匹配串:用于寻找模式串的原串。
模式串:需要在待匹配串中找到的子串。
前缀:从串首开始往后到串中(末)的一部分子串
后缀:从串中(首)开始往后到串末的一部分子串
公共前后缀:两个完全相等的子串,一个为前缀,一个为后缀,如abcab的ab即公共前后缀。
最长公共前后缀:公共前后缀中长度最长的一个。如abacaba中的aba。
前缀函数
定义
前缀函数是一个数组。第i个(下标从0开始)数字表示:截至下标为i的字符的前缀,其最长公共前后缀的长度。一般符号为\(Π\)。
如abacabab,其前缀函数为0, 0, 1, 0, 1, 2, 3, 2。对于i=5的情况,前缀为abacab,最长公共前缀为ab,前缀函数为2.
前缀函数的计算
对于第i个前缀函数pi[i],可以由前面的前缀函数递推而来。刚开始,我们需要比较s[pi[i-1]]与s[i](对比是否能延伸第i-1个前缀函数的最长公共前后缀,相等说明可以延伸。比如abaca,最长公共前后缀为a。加入b变为abacab,由于两个b相等,所以最长公共前后缀从a延伸为ab。),如果相等,则pi[i] = pi[i-1] + 1。如果不相等,我们退而求其次。接下来,我们需要知道i-1的次长公共前后缀。根据相等的转换关系,令k = pi[i-1]为第一次需要查询的位置,不断令k = pi[k-1]直到k=0或s[k]==s[i]即可。
如abacabab,前缀函数为0, 0, 1, 0, 1, 2, 3, ?。i=7。初始时k取3,表示不加入b时(前7个字符)最长公共前后缀长度为3(aba),对比s[3]与s[7],发现'c' != 'b',因此需要取舍。要找不加入b时的次长公共前后缀,首先要知道,前三个和后三个相等(因为pi[6] = 3),即0-2与4-6相等。那还有0-?与?-6相等呢?查询pi[k-1]即pi[2],为1,表示0-0与2-2相等,又因为0-2与4-6相等,所以s[2]与s[6]相等,即0-0与6-6相等,找到次长公共前后缀为a,长度为1.然后发现,s[1] == s[i],可以延伸该公共前后缀,延伸后长度为1.
代码实现
for (i64 i = 1; i < s.size(); i++) {
k = pi[i - 1];
while (k && s[k] != s[i]) //保证k>0的情况下不断找公共前后缀
k = pi[k - 1];
pi[i] = k + (s[k] == s[i]);
}
KMP的实现
以下是“合并串”的KMP。将s2(模式串)与s1(待匹配串)连接起来,中间用不可能出现在s1和s2中的字符分隔。然后对合并串求前缀函数,在pi[i] == s2.size()的地方,找到了一次匹配。
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
string s1, s2, s;
i64 k, pi[2000100];
int main() {
cin >> s1 >> s2;
s = s2 + '#' + s1; //合并串
for (i64 i = 1; i < s.size(); i++) {
k = pi[i - 1];
while (k && s[k] != s[i]) //保证k>0的情况下不断找公共前后缀
k = pi[k - 1];
pi[i] = k + (s[k] == s[i]);
if (pi[i] == s2.size())
cout << i + 1 - s2.size() * 2 << '\n'; //打印位置
}
return 0;
}

浙公网安备 33010602011771号