KMP
我的理解 是一个通过预处理储存字符串自身具有的前后缀一致性质来达到快速处理“字符串匹配”“字符串重复”的问题的算法,
核心是 \(next\) 数组。
以字符串匹配为例子,简单阐述一下KMP算法相比于暴力算法的优越性。
举例问题是字符串 \(A\) 中有多少个字符串 \(B\);
在 \(abababaac\) 中找 \(ababab\)
假设已经处理到这种情况:
$ \ \ \ \ \ \ \ \ 1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 7 \ 8 \ 9$
$A = a\ b\ \underline{a\ b \ a \ b \ a} \ a \ c $
\(B = \ \ \ \ \ \ \underline{a \ b \ a \ b \ a} \ b\)
假设是从A的第3位暴力处理到了第7位
现在要处理第8位,按照暴力算法的思路,由于第8位的字符对应不上,所以要将 \(B\) 的首位调到第4位重新去匹配
但是这其中可以优化,注意到 \(B\) 串中 \(ababa\) 的前缀 \(aba\) 和后缀 \(aba\) 相同,我可以直接把 \(B\) 串后移两位,变成如下情况继续匹配:
$ \ \ \ \ \ \ \ \ 1 \ 2 \ 3 \ 4 \ 5 \ 6 \ 7 \ 8 \ 9$
$A = a\ b \ a\ b \ \underline{a \ b \ a} \ a \ c $
\(B = \ \ \ \ \ \ \ \ \ \ \ \underline{a \ b \ a} \ b \ a \ b\)
这样子免去了暴力算法的又一次从头开始匹配,而如何让 \(B\) 去移动,就是KMP算法要处理的。(虽然这个例子优势不明显)
next数组
首先要明确这是对于字符串自身而言的性质
\(next[i]\) 表示的是字符串 \(S\) 的子串 \(S[1 … i]\) 中满足真前缀和真后缀相等的最长长度 \(len\) ,即满足\(S[1…len] = S[i - len + 1…i]\)的 \(len\) 的最大值
举个例子, \(abababc 的next[1] = 0,next[2] = 0, next[3] = 1,(a),next[4] = 2, (ab), next[5] = 3, (abc), next[6] = 4, (abcd), next[7] = 0\);
求法如下:
\(1.假设next[1]到next[i - 1]已经求出来;
\\2.扩展匹配长度j,匹配失败时令j = next[j],直到j = 0;
\\3.如果扩展成功,j++, next[i]=j。\)
void prenet(string a) {
int n = strlen(a);
for (int i = 2, j = 0; i <= n; ++i){
while (a[i] != a[j + 1] && j > 0) j = net[j];
if (a[i] == a[j + 1]) ++j;
net[i] = j;
}
}
附上一些容易想到的拓展点:
\(1.字符串匹配时可以直接把模式串放在待匹配串前面,\\中间用特殊字符隔开后(避免求next时匹配到超出模式串的字符)\\直接求next数组,那么next[i] = 模式串长度的,就是匹配成功的。\)
\(2.next[i],next[next[i]],next[next[next[i]]]等等都是位置i的可匹配长度,\\next[i]储存的是i位置的可匹配长度最大值,\\那么最小值也可以通过递推直到next[j]=0,j就是最小值\)