扩展kmp算法
原文链接:扩展kmp算法
**问题定义:**给定俩个字符串,文本串S,模式串T,长度分别为n和m,下标从0开始定义。定义Extend[]数组,Extend[i] 等于 S[i]…S[n-i] 与 T 的最长相同的前缀长度,现在问题是求出所有Extend[i]。举个例子,看下表:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
S | a | a | a | a | a | b | b | b |
T | a | a | a | a | a | c | ||
Extend[i] | 5 | 4 | 3 | 2 | 1 | 0 | 0 | 0 |
如果在S的某个位置 i 有Extend[i] = m,则可知在 S 中找到了模式串 T,并且匹配的首位是 i 。而且,扩展kmp算法可以找到 S 中所有 T 的匹配。
一.算法流程
(1)
如上图,假设当前遍历到 S 串位置 I ,即Extend[0]…Extend[i-1]这 i 个位置的值已经计算得到了。设置俩个变量 a 和 p 。代表以 a 为起始位置的字符匹配成功的最右边界,也就是 “p = 最后一个匹配成位置 + 1“。相较于字符串 T 得出,S[a…] 等于T[0…p-a]。
再定义一个辅助数组int Next[], 其中Next[i] 的含义为:T[i]…T[m-1] 与 T 的最长相同的前缀长度,m为串 T 的长度。举个例子:
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
T | a | a | a | a | a | c |
Next[i] | 6 | 4 | 3 | 2 | 1 | 0 |
(2)
S[i] 对应 T[i - a], 如果 i + Next[i - a] < p, 如上图,三个椭圆长度相同,根据Next数组的定义,此时 Extend[i] = Next[i - a]。
(3)
如果i + Next[i - a] == p呢? 如上图,三个椭圆都是完全相同的, S[p] != T[p - a] 且 T[p - i] != T[p - a], 但S[p] 有可能等于 T[p - i], 所以我们可以直接从 S[p] 与 T[p - i]开始往后匹配,加快了速度。
(4)
如果i + next[i - a] > p呢?那说明S[i…p)与T[i-a…p-a)相同,注意到S[p] != T[p - a]且T[p - i] == T[p - a],也就是说S[p] != T[p - i],所以就没有继续往下判断的必要了,我们可以直接将extend[i]赋值为p - i。
(5)
最后,就是求解 next 数组。我们再来看下next[i]与extend[i]的定义:
- next[i]: T[i]…T[m - 1]与 T 的最长相同前缀长度;
- extend[i]: S[i]…S[n - 1]与 T 的最长相同前缀长度。
恍然大悟,求解next[i]的过程不就是 T 自己和自己的一个匹配过程嘛,下面直接看代码。
二.代码
#include <iostream>
#include <string>
using namespace std;
/* 求解 T 中 next[],注释参考 GetExtend() */
void GetNext(string & T, int & m, int next[])
{
int a = 0, p = 0;
next[0] = m;
for (int i = 1; i < m; i++)
{
if (i >= p || i + next[i - a] >= p)
{
if (i >= p)
p = i;
while (p < m && T[p] == T[p - i])
p++;
next[i] = p - i;
a = i;
}
else
next[i] = next[i - a];
}
}
/* 求解 extend[] */
void GetExtend(string & S, int & n