了解kmp算法之前先要知道为什么要有kmp算法
1.朴素模式匹配
1.1基本思想:
从主串test 和模式串pattern 的第一个字符开始,将两字符串的字符一一比对,如果出现某个字符不匹配,主串回溯到第二个字符,子串回溯到第一个字符再进行一一比对。如果出现某个字符不匹配,主串回溯到第三个字符,子串回溯到第一个字符再进行一一比对…循环一直到子串字符全部匹配成功。
1.2例如:
第一次:不匹配
第二次:主串从第2个字符,模式串从头开始,直到b与c不匹配
第三次:主串从第3个位置开始,模式串从头开始,a与c不匹配
第四次:主串从第4个位置开始,模式串从头开始,匹配成功
朴素模式匹配的缺点:
当主串与模式串能部分匹配时, 主串的扫描指针i经常回溯,导致时间开销增加。
2.kmp算法
2.1基本思想
当字串与模式串不匹配的时候,主指针不需要再次回溯,只需要调整模式串的位置
例如:
当扫描到 a , f 不匹配时,如果是朴素模式匹配那么 i指针会退到第二个位置,j指针会退到开头位置
像这个样子:
而如果是kmp算法,会调整 j 的位置,而不使 i 指针回退
实现这个功能那就必需要一个next数组
2.2next数组
kmp算法最主要的就是next数组
next数组作用:
next[i]的值表示下标为i的字符前的字符串最长相等前后缀的长度。
表示该处字符不匹配时应该回溯到的字符的下标
2.3next数组的实现
(1)初始化 i ,j,next[0]。i 指针指向前缀的最大长度,j 指针指向后缀的最大长度
(2)当i,j指针指的元素不相等时
(3)当i,j指针指的元素相等时
(4)next数组赋值或调整 j 指针的位置
2.4代码实现
这里初始化为next[0]=-1,j=0,k=-1,之后得到 next[i] 数组就是对应的值
如果初始化为next[1]=0,j=1,k=0,则需要初始数组空一格位置
void GetNext(char* p, int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1; //k指向前缀
int j = 0; //j指向后缀
while (j < pLen - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k]; //回退因为如果此时最大前后缀不满足,则继续看前一个元素的最大前后缀值
}
}
}
2.5next 数组举例
逻辑上可以理解为i,j分别指向两个相同的串,从而代表前缀和后缀。实际只是在一个串操作
此时i,j指的元素不相同,需要缩小最大前后缀的长度:j=next[j]
当i<length-1时退出循环,每次字符的前面匹配的字符的最大前后缀均以求出
2.6操作中j=next[j]解释
2.7kmp算法代码实现
int KmpSearch(char* s, char* p,int next[])
{
int i = 0;
int j = 0;
int sLen = strlen(s);
int pLen = strlen(p);
while (i < sLen && j < pLen)
{
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
//next[j]即为j所对应的next值
j = next[j];
}
}
if (j == pLen)
return i - j;
else
return -1;
}
测试: