目录
世上无难事,只要肯坚持。
前言
KMP算法是一种字符串匹配算法,它的全称是Knuth-Morris-Pratt算法,是由Donald Knuth、Vaughan Pratt和James Morris三位计算机科学家在1977年发明的。
KMP算法的意义在于缩小时间复杂度,其核心就是next数组。
一、什么是KMP?
KMP算法的主要思想是利用已经匹配过的部分字符的信息来避免不必要的比较,从而提高字符串匹配的效率。具体来说,KMP算法的核心是构建一个跳转表,该表记录了模式串中不匹配时应该跳过的位置。
在匹配时,我们从模式串的第一个字符开始,依次将模式串中的字符与文本串中的字符进行比较,如果匹配成功,继续比较下一个字符,否则根据跳转表中的记录跳过一些字符,再继续比较。这样可以避免在文本串中重复比较已经匹配过的字符,从而提高匹配效率。
KMP算法的时间复杂度为O(m+n),其中m和n分别是模式串和文本串的长度。因此,KMP算法是一种比较高效的字符串匹配算法,在实际应用中得到了广泛的应用。
二、跳转表---Next数组
1.什么是Next数组
next数组是KMP算法中的一个辅助数组,它与模式串相关。具体来说,next数组记录的是模式串中每个位置之前的子串中,最长的既是该位置的前缀,又是该位置的后缀的长度。
举个例子,对于模式串 P="ABCDABD",其 next 数组为 [0, 0, 0, 0, 1, 2, 0]。在这个例子中,next[5]表示的是 P[0...3] 这个子串中,既是 P[5] 的前缀,又是 P[5] 的后缀的最长子串的长度,即 "AB",因此 next[5]=2。
next数组的计算过程是在模式串中进行的,具体方法是利用前缀和后缀的重叠性质,从模式串的第一个字符开始,逐个计算每个位置的 next 值,直到计算到最后一个位置。因此,next数组是与模式串有关的。在匹配时,利用已经计算好的 next 数组,可以避免不必要的字符比较,从而加速匹配过程。
2.next数组的形参
具体来说,创建next数组的过程就是对-模式串-进行遍历,计算每个位置的next值。
// 定义串的结构
typedef struct
{
char *str;
int length;
}StrType;
这里用结构体封装,包含模式串及其长度
void getNext(const StrType *subStr, int next[])
{
int i = 1, j = 0;
next[1] = 0;
while (i < subStr->length)
{
if (j == 0 || subStr->str[i] == subStr->str[j])
{
++i;
++j;
next[i] = j;
}
else
{
j = next[j];
}
}
}
函数中的参数类型可以根据具体的编程语言和实现方式进行调整,但是需要包含模式串和它的长度,以及用来存储next数组的数组或向量等数据结构。
当前位置不匹配->从前面的(前后缀中找到公共部分的个数 + 1)再与之匹配,若不匹配,再从前面的(前后缀中找到公共部分的个数 + 1)再与之匹配,最坏情况的前一步就是没有公共部分(0 + 1)所以next数组值为 1 ,若还不匹配,next退回0。
注意!next数组的中某个元素的下一个绝对不会超过当前值+1
三、那next值为0是什么意思?
在KMP算法中,next数组记录的是匹配字符串中每个字符对应的最长公共前缀后缀的长度。如 果某个子序列的next数组值为0,那么说明这个子序列的第一个字符没有任何一个前缀和后缀 是相同的,也就是说它没有重复的部分,这个子序列和其它任何字符串的匹配都不会出现前缀和 后缀相同的情况,所以在匹配过程中,如果当前位置的字符与模式串中的字符不匹配,则直接跳 过该位置,从模式串的下一个位置开始匹配。因此,如果一个子序列的next数组值为0,意味 着在该子序列的匹配中,无法利用已经匹配的部分来加速后续匹配的过程。
我觉得 next数组中难理解的点就在:有重复的子串(公共子序列)判定上。
举个例子:
对于模式串中 : 圈起来的部分是重复内容,现在要求17的next值。
17的next的值跟不断去找17前一个next的情况,就是跟next的关系
上图中,如果17位置不匹配。
不管17,跳到16的next数组值的位置---->也就是说看他前面的数组的前后缀关系,找到(公共子序列数 + 1)的位置,再重复进行比较。
最后
int indexKMP(const StrType *str, const StrType *subStr, const int *next)
{
int i = 1;
int j = 1;
while (i <= str->length && j <= subStr->length)
{
if (j == 0 || str->str[i] == subStr->str[j])
{
++i;
++j;
}
else
{
j = next[j];
}
}
if (j > subStr->length)
{
return i - subStr->length;
}
else
{
return 0;
}
}
四、总结
KMP算法的时间复杂度是O(m+n),其中m和n分别为模式串和文本串的长度。因为我们只需要对模式串构建一次跳转表,然后在匹配时按照跳转表进行比较,所以时间复杂度是线性的。当模式串和文本串较长时,KMP算法比朴素算法的效率要高得多。