KMP算法详解
一、什么是KMP算法
我们在模拟strstr函数时,有两种方法
(1)BF暴力算法
(2)KMP算法
在BF算法中,每一次的不匹配都会让子串回到初始位置,主串回到前一个匹配字符的下
一个字符,开始匹配。那么KMP算法是一种尽量减少主串和子串的的匹配次数的改进的
算法。
时间复杂度O( m + n)
KMP和BF唯一的区别:
主串的 i 不会回退,并且子串的 j 也不会移动到 0 号位置
子串中每一个字符都对应一个不匹配时应该回退的位置K,我们用一个和
子串相等长度的next数组来存放K。求出这个next数组就是KMP算法的关键
二、next数组的求法
K值规则:
1.找到匹配成功的两个相等的真子串(不包含本身),一个以下标 0 开始,另一个以下标 i - 1 结尾(两个真子串可以重合),这两个真子串的长度就是下标 i 字符对应的 K 值;
2.不管什么数据它对应的next数组都是:next[0] = -1, next[1] = 0;有的地方next[0] = 0. next[1] = 1,(只需要在之前的基础上求出的K值全部 + 1)
列如:
练习:
按照规则1.找到匹配成功的两个相等的真子串(不包含本身),一个以下标 0 开始,另一个以下标 i - 1 结尾(两个真子串可以重合),这两个真子串的长度就是下标 i 字符对应的 K 值;
通过以上的图解我们可以得出以下结论:当我们知道子串 j 下标字符的 K 值时,我们可以通过判断子串 j + 1 下标的元素,和主串 i + 1 下标的字符是否相等来确定 j + 1 下标字符对应的 K 值
如果 sub[j] = sub[k],则 next[j + 1] = k + 1
如果 sub[j] != sub[k], 则k回退,直到 sub[j] = sub[k] 或者 k = -1
三、C语言代码实现
(1)找子串的函数
基本上和BF算法是差不多的,唯一不同就是,主串的 i 不会回退,而子串有其特定的回退值
int KMP(const char* dest, const char* src, int pos)
{
assert(dest && src);
int lens = strlen(dest);
int lensub = strlen(src);
//创建next数组
int* next = (int*)malloc(sizeof(int)*lensub);
assert(next);
GetNext(next, src);
// 从pos开始遍历主串
int i = pos;
// 从0开始遍历子串
int j = 0;
while (i < lens && j < lensub)
{
if (j == -1 || dest[i] == src[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
if (j == lensub)
{
return i - j;
}
else
{
return -1;
}
}
(2)创建next数组的函数
这里创建next函数时,使用的下一项的 i下标,所以使用公式应该是 next[i] = K + 1;比较的应该是 sub[i - 1] 与 sub[k]
void GetNext(int* next, const char* sub)
{
int lensub = strlen(sub);
next[0] = -1;
next[1] = 0;
int i = 2;// 下一项,这里的i是下一项的,所以使用公式应该是 next[i] = K + 1;比较的应该是 sub[i - 1] 与 sub[k]
int k = 0;// 前一项的K
while (i < lensub)// 子串还没有遍历完
{
if (k == -1 || sub[i - 1] == sub[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];
}
}
}