所以,具体再从什么地方开始匹配,取决于我们的next[],那么next[]怎么求呢?
kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。
而next数组的值是代表着字符串的前缀与后缀相同的最大长度(不能包括自身)。
下面我们以字符串"ababcabcdabcde"为例,求它的next数组:
j从0下标开始,很显然 j-1 没有元素,所以我们把它设置为-1,j++向后移
j来到1下标,j-1=0,那么就是以a开头,以a结尾的字符串,但是不能是同一个串,所以next[1]=0, j++向后移
j来到2下标,j-1=1,那么就是以a开头,以b结尾的字符串(不能是同一个串)很显然是没有的,那么next[2]=0, j++向后移
j来到3下标,j-1=2,那么就是以a开始,以a结尾的串,串的长度为1,所以next[3]=1,然后j++向后移。
j来到4下标,j-1=3,那么就是以a开始,以b结尾的串,串的长度为2,所以next[3]=2,然后j++向后移。
j来到5下标,j-1=4,那么就是以a开始,以c结尾的串,很显然没有,所以next[5]=0,然后j++向后移。
j来到6下标,j-1=5,那么就是以a开始,以a结尾的串,串的长度为1,所以next[6]=1,然后j++向后移。
剩下的也同上面一样,就不在一一讲解,在这里把图给大家
到这里对如何求next数组应该问题不大了,接下来的问题就是,已知next[i]=k;怎么求next[i+1]=?,我们通过肉眼可以很清楚的看出,但是如何让机器知道呢?
如果我们能通过next[i]的值,经过一系列的转换得到next[i+1]的值,那我们就能实现这部分了。
void GetNext( char*sub ,int*next)
{
int lensub = strlen(sub);
next[0] = -1;
next[1] = 0;
int i = 2;
int k = 0;
while (i<lensub)
{
if (k == -1 || sub[i - 1] == sub[k])
{
//如果i-1与k相等,那么就找到了以字符X开头,以字符X结尾的串
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k]; //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
}
}
}
/**
str 目标串
sub 模式串
pos 从模式串的pos位置开始匹配
return 找到模式串在目标串当中的下标
*/
int KMP( char* str, char* sub ,int pos)
{
int lenstr = strlen(str);
int lensub = strlen(sub);
if (str == NULL || sub == NULL) //两串不为空
{
return -1;
}
if (pos < 0 || pos >= lenstr) //开始查找位置不合法
{
return -1;
}
int* next = (int*)malloc(lensub * sizeof(int));//定义一个和字串一样长的数组
int i = 0;//遍历主串
int j = pos;//遍历子串
GetNext(sub, next);
while (i < lenstr && j < lensub)
{
if (j==-1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (j >= lensub)
{
return i - j;
}
else
return -1;
}
int main()
{
printf("%d\n", KMP("ababcabcdabcde", "abcd",0));//5
printf("%d\n", KMP("ababcabcdabcde", "abcdd",0));//-1
printf("%d\n", KMP("ababcabcdabcde", "a",0));//0
}
为什么要优化next数组呢?
void GetNextValue( char* sub, int* nextvalue)
{
int lensub = strlen(sub);
nextvalue[0] = -1;
nextvalue[1] = 0;
int i = 2;
int k = 0;
while (i < lensub)
{
if (k == -1 || sub[i - 1] == sub[k])
{
if(sub[i]==sub[k])
{
nextvalue[i] = nextvalue[k];//如果回退到的位置的数据 等于当前位置对应的数据,则回退到那个位置的nextvalue值
}
else //(sub[i] != sub[k])如果回退到的位置的数据 不等于当前位置对应的数据,则当前位置回退到自己next值,即k的值
{
nextvalue[i] = k;
}
i++;
k++;
}
else
{
k = nextvalue[k]; //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
}
}
}
//next数组
void GetNext( char*sub ,int*next)
{
int lensub = strlen(sub);
next[0] = -1;
next[1] = 0;
int i = 2;
int k = 0;
while (i<lensub)
{
if (k == -1 || sub[i - 1] == sub[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k]; //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
}
}
}
//nextvalue数组
void GetNextValue( char* sub, int* nextvalue)
{
int lensub = strlen(sub);
nextvalue[0] = -1;
nextvalue[1] = 0;
int i = 2;
int k = 0;
while (i < lensub)
{
if (k == -1 || sub[i - 1] == sub[k])
{
if(sub[i]==sub[k])
{
nextvalue[i] = nextvalue[k];//如果回退到的位置的数据 等于当前位置对应的数据,则回退到那个位置的nextvalue值
}
else //(sub[i] != sub[k])如果回退到的位置的数据 不等于当前位置对应的数据,则当前位置回退到自己next值,即k的值
{
nextvalue[i] = k;
}
}
i++;
k++;
}
else
{
k = nextvalue[k]; //如果不相等,让k一直回退,直到sub[i-1]==sub[k]
}
}
}
int KMP( char* str, char* sub ,int pos)
{
int lenstr = strlen(str);
int lensub = strlen(sub);
if (str == NULL || sub == NULL) //两串不为空
{
return -1;
}
if (pos < 0 || pos >= lenstr) //开始查找位置不合法
{
return -1;
}
int* next = (int*)malloc(lensub * sizeof(int));//定义一个和字串一样长的数组
int i = 0;//遍历主串
int j = pos;//遍历子串
GetNextValue(sub, next);
//GetNext(sub,next);
while (i < lenstr && j < lensub)
{
if (j==-1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (j >= lensub)
{
return i - j;
}
else
return -1;
}
int main()
{
printf("%d\n", KMP("ababcabcdabcde", "abcd",0));//5
printf("%d\n", KMP("ababcabcdabcde", "abcdd",0));//-1
printf("%d\n", KMP("ababcabcdabcde", "a",0));//0
}