KMP算法的核心思想:避免不必要的回溯,主串不进行回溯,子串在每个位置的回溯位与当前位置之前字符与自身开始几位的字符匹配程度来决定。
如上图所示,当在下标i=5处失配。由图可知:
P[0] ... P[j - 1] == S[i - j] ... S[i - 1]
P[0] ... P[k - 1] == S[i - k] ... S[i - 1]
S[i - k] ... S[i - 1] == P[j - k] ... P[i - 1]
可推出:P[0] ... P[K - 1] == P[j - k] ... P[j - 1]
则j不必退回0,i也不必退回 i - j + 1处,只需j退回到字符k处,如下:
然后 i 和 j 接着往下匹配即可,失配之后继续按照上述方法回退
那么问题来了,k值如何得到呢,KMP算法给了我们一种思路即引入next数组(next数组长度和模式串S长度相同)
next[i]存储的数值是:从S[0] - S[i]组成的字符串中两个相等最大真子串的长度,这两个串满足以下限制条件:
a.第一个串以0下标开始(一个真子串以原子串的开头作为开头)
b.第二个串以j-1结束(另外一个真子串以匹配成功的最后一个字符作为结尾
S串的next数组为:
//O(m)
void GetNext(const char *p, int *next)
{
next[0] = -1;
next[1] = 0;
int j = 1;
int k = 0;
int lenp = strlen(p);
while (j + 1 < lenp)
{
if (k == -1 || p[k] == p[j])
{
// next[j + 1] = k + 1;
// j++;
// k++;
next[++j] = ++k;//通过j的值推j+1的值
}
else
{
k = next[k];
}
}
}
1.如下图所示,跟BF算法相同,第一次当i = j = 5时失配
2.根据KMP算法,此时 j 没有必要一定退到0,只需回退到k=next[j]位置即可,而i可以不用回退,如下图所示:
3.然后判断P[j]和S[i]是否相等,如果相等则进行i++,j++操作,很明显,此时不相等,那么 j 继续回退到next[j],如下图:
4.此时P[j] == S[i],则i++,j++
重复上述步骤,直到 j 走到字符串P末尾,则说明从i - j处开始P串是S串的子串。如果i走到S串末尾 j 都没有走到P串末尾,则说明串P不是S串子串。
//O(m+n)
int KMP(const char *s, const char *p, int pos)
{
assert(s != NULL && p != NULL);
int lens = strlen(s);
int lenp = strlen(p);
if (lens < lenp)
{
return -1;
}
int i = pos;
int j = 0;
int *next = (int *)malloc(sizeof(int) * lenp);
assert(next != NULL);
GetNext(p, next); //O(m)
// O(n)
while (i < lens && j < lenp)
{
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
if (j >= lenp)
{
return i - j;
}
return -1;
}