1.什么是KMP算法?
KMP算法(Knuth-Morris-Pratt Algorithm),是由Donald Knuth、Vaughan Pratt和James H. Morris三位计算机科学家于1970年代共同提出的字符串搜索算法。它是一种改进的字符串匹配算法,主要用于在一个较大的文本串(通常称为“主串”)中查找一个模式串(Pattern)出现的所有位置。
在朴素的字符串匹配算法中,当模式串与主串部分匹配但最后发生失配时,需要将模式串回溯到起始位置重新开始匹配。而KMP算法通过预处理模式串并构建一个前缀函数或Next数组,记录了模式串中每一个前缀与后缀的最大相同前缀长度,这样在失配时无需回溯整个模式串,而是利用这个已知信息直接移动模式串指针至适当的位置继续匹配,从而极大地提高了匹配效率。
KMP算法的时间复杂度为O(n + m),其中n是主串的长度,m是模式串的长度。即使在最坏的情况下,也能避免不必要的字符比较,保证了高效的搜索性能。
2.举例说明
假设我们有一个主串 S = "ababcabcabcdabcde"
,我们要在其中查找模式串 P = "abcabc"
。
朴素的字符串匹配方法是逐个比较字符:
- 从主串开始位置开始,将
P[0]
与S[0]
进行比较。 - 如果相等,则继续比较
P[1]
和S[1]
,直到某个位置不匹配(例如S[3] != P[3]
)或者完全匹配成功(即P[m-1]
与S[n-m+1]
匹配)。 - 不匹配时,主串需要回溯一位,然后重新从头开始比较。
而对于KMP算法,首先预处理模式串得到Next数组(也称部分匹配表),如下所示:
- P = "abcabc"
- Next数组:[0, 0, 0, 1, 2, 0]
接下来进行搜索:
- 将
P[0]
与S[0]
比较,相等,继续比较下一个。 - 当
S[3] != P[3]
发生失配时,由于Next[3]
的值为1,我们知道不需要回溯整个模式串,而是直接将模式串右移1位,使得P[1]
对齐到S[3]
继续比较。 - 继续这个过程,最终找到第一个匹配的位置
S[3]
。
通过KMP算法,我们可以避免不必要的字符回溯和重复比较,从而提高效率。在上述例子中,最终会找到两个匹配的位置,分别是 S[3]
和 S[9]
。
那么Next数组中的值怎么来的呢?
Next数组(部分匹配表)的构建是KMP算法的核心预处理步骤。它记录了模式串中每个位置之前的子串中,最长相同前后缀长度的信息。
以字符串 "abcabc" 为例来说明如何构建Next数组:
P[0]
为起始字符,没有前缀和后缀,因此Next[0] = 0
。- 检查
P[1]
,其前缀只有空串,后缀为 "b",两者无相同部分,所以Next[1] = 0
。 - 对于
P[2]
,它的前缀和后缀分别是 "a" 和 "bc",它们没有相同的字符,所以Next[2] = 0
。 - 当检查到
P[3]
时,前缀为 "ab",后缀也为 "bc",这时发现有一个共同的前缀 "b",但我们需要找到最长的相同前后缀,所以Next[3]
的值应该是 "b" 前面那个位置的 Next 数组值,即Next[1]
(因为这里没有更长的相同前后缀),所以Next[3] = 0
。 - 到
P[4]
时,前缀为 "abc",后缀为 "c",没有公共部分,因此Next[4] = 0
。 - 最后检查
P[5]
,前缀为 "abca",后缀为 "bc",这里有公共部分 "bc",且是最长相同前后缀,那么Next[5]
应该指向这个公共部分在原字符串中的位置(而不是它自身的位置),也就是Next[3]
,因此Next[5] = 1
。
最终得到的Next数组为 [0, 0, 0, 1, 2, 0]
。
注意:上述例子中,在构造Next数组时忽略了计算过程中可能存在的更长相同前后缀,实际操作时需要确保找到最长相同前后缀长度。正确的Next数组应为 [0, 0, 0, 0, 1, 2]
,其中对于 P[3]
来说,并没有长度大于0的相同前后缀,所以 Next[3] = 0
。
所谓的前缀和后缀,
前缀(prefix)是指从字符串的起始位置开始到任意一个位置结束的部分。例如,在字符串 "abcde" 中,前缀包括:
- 空串 ""
- "a"
- "ab"
- "abc"
- "abcd"
- "abcde"
后缀(suffix)则是指从字符串的某个位置开始直到末尾的部分。对于同一字符串 "abcde",其后缀包括:
- "e"
- "de"
- "cde"
- "bde"
- "bcde"
- "abcde"
在KMP算法构建Next数组的过程中,我们需要找到模式串P每个位置i之前的子串所对应的最长相同前后缀长度。这里的“相同前后缀”指的是同一个子串既是该位置之前的某个子串的后缀,也是它自己的前缀。
举个例子,对于字符串 "abcaba",在索引为5的位置时,前缀是 "abcaba",后缀是 "ba",它们的公共部分是 "a",所以这个位置的最长相同前后缀长度就是1。而在索引为3的位置处,前缀是 "abcab",后缀是 "ab",它们有相同的 "ab" 子串,因此最长相同前后缀长度为2。