要了解 KMP 算法背后的逻辑,您首先应该了解,此 KMP 算法如何优于蛮力算法。
Idea
在模式发生变化之后,朴素算法已经忘记了有关先前匹配符号的所有信息。因此,它可能会一次又一次地将文本符号与不同的模式符号进行重新比较。这导致其最坏情况复杂度为 Θ(nm)(n:文本长度,m:模式长度)。
Knuth、Morris 和 Pratt [KMP 77] 的算法利用了先前符号比较获得的信息。它永远不会重新比较与模式符号匹配的文本符号。因此,Knuth-Morris-Pratt 算法的搜索阶段的复杂度在 O(n) 之内。
然而,为了分析其结构,必须对模式进行预处理。预处理阶段的复杂度为 O(m)。由于 m<=n,Knuth-Morris-Pratt 算法的整体复杂度在 O(n) 内。
文字:bacbababaabcbab 图案:abababca
在 brute-force 方法中,将图案一张一张地滑过文本并检查是否匹配。如果找到匹配项,则再次滑动 1 以检查后续匹配项。
void search(char *pat, char *txt)
{
int M = strlen(pat);
int N = strlen(txt);
/* A loop to slide pat[] one by one */
for (int i = 0; i <= N - M; i++)
{
int j;
/* For current index i, check for pattern match */
for (j = 0; j < M; j++)
{
if (txt[i+j] != pat[j])
break;
}
if (j == M) // if pat[0...M-1] = txt[i, i+1, ...i+M-1]
{
printf("Pattern found at index %d \n", i);
}
}
}
上述算法的复杂度为 O(nm)。在上述算法中,我们从未使用我们处理的比较数据,
Bacbababaabcbab //let I be iterating variable over this text
Abababca //j be iterating variable over pattern
当 i=0 且 j=0 存在不匹配时 (text[i+j]!=pat[j]),我们递增 i 直到匹配。当 i =4 时,有一个匹配(text[i+j]==pat[j]),递增 j 直到我们找到不匹配(如果 j= patternlength 我们找到了模式),在给定的示例中,我们在 j= 处找到不匹配5 当 i=4 时,文本中的 idex 4+5=9 发生不匹配。匹配的子字符串是 ababa , **
Why we need to choose longest proper prefix which is also proper suffix :
** 从上面:我们看到不匹配发生在 9 ,其中模式以子字符串 ababa 结尾。现在,如果我们想利用到目前为止所做的比较,那么我们可以跳过(增加)i 超过 1,那么比较的数量将减少,从而提高时间复杂度。
现在我们可以对处理过的比较数据“ababa”有什么好处。仔细看:字符串 ababa 的前缀aba与文本比较匹配,后缀aba也是如此。但是 'b' 与 'a' 不匹配</p>
Bacbababaabcbab
||||||
||||| x
|||| ||
ababab
但是根据朴素的方法,我们将 i 增加到 5。但是通过查看它知道,我们可以将 i =6 设置为下一次出现的 aba 出现在 6。因此,我们不是与文本中的每个元素进行比较,而是对模式进行预处理用于找到最长的正确前缀,它也是正确的后缀(称为边界)。在上面的 'ababa' 示例中,最长边界的长度为 3(即aba)。所以增加:子串的长度 - 最长边界的长度=> 5-3 =2。
如果我们的比较在 aba 失败,那么最长边界的长度是 1 并且 j=3 所以增加 2 。
有关如何预处理的更多信息:http ://www-igm.univ-mlv.fr/~lecroq/string/node8.html#SECTION0080
http://www.inf.fh-flensburg.de/lang/algorithmen/pattern /kmpen.htm