kmp算法详解-中
next数组求解
书接上回,上次我们讲了kmp算法匹配串前缀表(next)回溯的原理,但是理解了这些可能还是无法使用kmp算法去解字符串匹配类问题,之前我们都是人工计算next表,但是代码怎么计算出前缀表呢,这又是个头疼的问题。下面我们还是结合动图来讲。
- 假设待求解next表的数组为s
- 使用
i
来表示需要求解前缀表的子串结束位置,所以该位置的后缀一定是以字符s[i]
结尾,i
从1开始 - 使用
j
表示最长相等前缀的位置,j
从0开始 - next[0]恒为0
1、当s[i]==s[j]
时,我们后移i
和j
,且next[i]=j+1
2、当遇到s[i]!=s[j]
,为了方便观察,我们将原本的数组复制一份,分别标注i
和j
的位置,
i
之前的next数组是已知的,所以j
之前的next数组也是已知的,我们假设next[j-1]=m
- 因为
j
之前区域和i
之前长度为j
的区域是相等的,即蓝色部分,又因为next[j-1]=m
,所以区域1 == 区域2 == 区域3 == 区域4
- 那么我们将
j
移动到m处,一定可以和i
前面的m个字符匹配。 - 最后再将复制出来的数组还原,即得到了
j
需要回溯的位置。
3、此时只需要继续判断s[i]
和s[j]
是否相等,重复步骤一和步骤二即可得到next数组。
源代码如下:
void get_prefix_table(char *s, int next[])
{
int i = 1, j = 0;
int len = strlen(s);
for ( i = 1, j = 0; i < len;)
{
if (s[i] == s[j]){
next[i++] = ++j;
}else if (j == 0){
next[i++] = 0;
}else {
j = next[j - 1];
}
}
}
此时可以很轻松的写出kmp算法的实现了,下面是个示例代码:
void get_prefix_table(char *s, int next[])
{
int i = 1, j = 0;
int len = strlen(s);
for ( i = 1, j = 0; i < len;)
{
if (s[i] == s[j]){
next[i++] = ++j;
}else if (j == 0){
next[i++] = 0;
}else {
j = next[j - 1];
}
}
}
int strStr(char* haystack, char* needle) {
int next[strlen(needle)] ;
int i = 0, j = 0;
next[0] = 0;
get_prefix_table(needle, next);
for (i = 0, j=0; i < strlen(haystack);)
{
if (haystack[i] == needle[j])
{
i++;
j++;
if (j == strlen(needle))
return i - j;
continue;
}
if (j != 0)
j = next[j-1];
else
i++;
}
return -1;
}
结束语
虽然kmp算法已经很高效了,但是仍然有改进的空间,因为画图太耗费时间,所以我准备再分一期讲解,敬请期待。