查阅大佬博客https://blog.csdn.net/v_july_v/article/details/7041827
1.kmp是什么
kmp是一个可以让文本串不用回溯的字符串算法,什么意思呢?好比文本串”BBC ABCDAB ABCDABCDABDE“,模式串”ABCDABD“,他们从第一个开始匹配,i指向文本串当前匹配的字符位置,不用回溯的意思是文本串的i可以从0一直走到22,而不用返回。这也是kmp的精妙之处。
那么他是怎么做到可以让文本串指针不用回溯呢?
第一轮匹配
BBC ABCDAB ABCDABCDABDE
ABCDABD ->向后匹配
BBC ABCDAB ABCDABCDABDE i=11
ABCDABD j=7
到此时模式串中D与文本串中的空格不匹配了,我们想一下蛮力算法此时会怎么做
BBC ABCDAB ABCDABCDABDE i=6
ABCDABD j=1
我们可以看到,蛮力算法让i从11,回溯到6(该字符串是从1开始算起),我们现在来看kmp算法的匹配方式
BBC ABCDAB ABCDABCDABDE i=11
ABCDABD j=3
i没有进行回溯,而是将j做了改变。因为当D和空格不匹配的时候,与D相邻的AB已经匹配成功,所以我们寻找模式串中在此AB之前是否还有AB可以与此AB匹配,以减少中间很多的不必要匹配。 这在做较长的匹配时,效率将提升很多。那么如何实现只动模式串而不让文本串回溯呢?!
这里将出现kmp算法中最重要的一个数组 next数组,当匹配不成功时,只用将指针指向该指针对应的next数组就ok了。
这里先给出kmp算法的实现,next数组求法在下面给出
int KmpSearch(char* s, char* p)
{
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]),向前继续匹配
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;
}
2.什么是next数组
next数组是仅对模式串来说的,可以看作是模式串的自我匹配。next数组是在当前字符前的字符串中,前缀字符串与后缀字符串的最大匹配长度。这句会可能不是很好理解,我们来举个例子,先说什么是前缀字符串和后缀字符串
ABCDABD
1> 什么是前后缀字符串:
模式串"ABCDABD"中 ”A","AB","ABC",,,,,,,只要从第一个字符 “A"开始数的子符串都叫做前缀字符串
那么后缀字符串就好理解了,就是从要数的字符往前数任意字符数,就叫做后缀字符串。
2> 什么叫最大前后缀字符串匹配数
还是上面的字符串,我们现在要求第二个字符‘D'前最大前后缀字符串的匹配长度,我们可以看到’D'之前的匹配的前后缀字符串只有”AB".所以next[D]=2(这里的D是第二个D),我们用下标表示是不是就是next[6]=2(下表从0开始)。那我们是不是可以直接用next[6]表示第6个字符下一个跳转的位置呢?我们看next[6]=2,下标为2的字符是C,现在是不是和上文kmp的匹配方式相同了呢,当D不匹配时,换做C进行匹配。为什么这样是正确的呢?我们知道next数组表示的最大匹配长度,那这个长度是不是就是前缀字符串的长度,而我们要做的就是跳转的就是前缀字符串的后一个字符进行比较,又因为字符串下标都是从第0位开始的,前缀字符串是从第一个开始的,所以前缀字符串的长度是不是就表示当前字符要跳转的目标字符。
void getnext(char s[]){
int i=0,j=-1;
next[0]=-1;
while (i<len){
//len表示模式串长度
if(j == -1 || s[i]==s[j])
//如果相等或者是第一个字符,就对他下一个位置进行复制
next[++i]=++j;
else j=next[j];
//否则向前跳转,直到找到相同的为止
}
}
建议多看几道题加深理解
如有错误,欢迎指正