KMP算法的应用。记WORD的长度为m,TEXT的长度为n, 暴力法的时间复杂度为O(nm), KMP算法可将复杂度降至O(n+m).
KMP算法的总体思想是让遍历TEXT的指针不回溯,从而保证关于n的线性复杂度。
为了实现TEXT指针j不回溯这一点,则只能要求WORD指针i回溯。现在问题在于,每次WORD[i]与TEXT[j]匹配失败后,i应该回溯到哪里呢?答案是回溯到k处,k等于字符串WORD[0:(j-1)]公共前缀后缀的最大长度。例如,字符串”aba”对应的最大公共前缀后缀是”a”, 字符串”abaab”对应的最大公共前缀后缀是”ab”,字符串”aaaa”对应的最大公共前缀后缀是”aaa”(注意前缀和后缀不能是字符串本身)。由于后缀WORD[(j-k):(j-1)] == TEXT[(i-k):(i-1)],则前缀WORD[0:(k-1)] == TEXT[(i-k):(i-1)],接下来只要继续比较WORD[k]与TEXT[i]是否相同即可。
将WORD数组的最大公共前缀后缀信息保存在数组next中,next[j]表示WORD[0:(j-1)]的最大共同前缀后缀长,并定义next[0] = -1表示当WORD的第一个字符与TEXT[i]不匹配时需要TEXT指针i后移。从而KMP算法的程序分为两个部分,一部分计算next数组,另一部分遍历TEXT数组计算匹配个数。
int kmp()
{
int cnt = 0, i = 0, j = 0, word_len = strlen(word);
while (j < strlen(text))
{
if (i == -1 || word[i] == text[j])
{
i++;
j++;
}
else
{
i = next[i];
}
if (i == word_len)
{
cnt ++;
i = next[i];
}
}
return cnt;
}
该部分复杂度为O(n),因为两次执行j++之间每次执行i = next[i]的次数不会超过上次j++连续执行的次数,因此while循环的执行次数上界为两倍的j++执行次数,即2n.
计算next数组
void cal_next() // 计算word字符串的最大共同前缀后缀长数组, next[i]: word[0:i-1]的最大共同前缀后缀长
{
next[0] = -1;
int i = -1, j = 0;
while (j < strlen(word))
{
if (i == -1 || word[i] == word[j])
{
next[++j] = ++i;
}
else
{
i = next[i];
}
}
}
去B站上听了印度程序员讲的kmp算法,听了两遍懂了,果然对于算法这种东西,还是有一个好例子跟详细的流程更容易懂啊
书上写的都是什么狗屁东西,看了半天都尼玛没看懂,根本没流程。。