kmp算法本身,解决的是判断模板字符串T,是否是字符串s的子串的问题。
当只需判断子串首次出现的位置,或是否包含子串。可以用库函数strstr(s,t )代替,判断t是否为s的子串来代替。该函数的返回值是t首次出现的位置,如果t不是s的子串则返回NULL。该函数的复杂度与kmp类似。
kmp算法主要分为两个部分第一部分是求解nexta数组(注意此处不能写next因为肯能会和C++的库变量重名,导致编译错误),第二部分利用nexta数组进行匹配。
先说为什么要求nexta数组。朴素的匹配方法。当发现字符串不匹配时,就要重头开始重新匹配,造成很高的复杂度(m*n)。如下面三组图片所示(图一图二图三图四,来自http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html)
图一
图二
图三
kmp算法对模式串进行处理,得出当匹配到不相等的地方时,指向模式串的指针重新指向的位置(朴素法的指针指向的位置是0)。且指向母串的指针始终是不会向左移动的,及只向右移动。当发现不匹配时利用nexta数组产生的效果。如图四所示。只有j改变了i不动,再次判断。使得复杂度是线性的。
图四
那么nexta数组的含义是什么呢?
设进行处理的字符串为s。nexta[i] = k.k是使得s[0...k-1] == s[i - k,i - 1]成立的最大值。
也就是求s的前缀,和以i-1结尾的后缀的最大值。
这种后缀等于前缀的模式保证移动是合适的
那么如何求解nexta数组呢?
此处借鉴“北京小王子”的方法
入口:点击打开链接
http://www.cnblogs.com/tangzhengyue/p/4315393.html
下面我们看一个彩图:
下面我们用数学归纳法来解决这个填值的问题。
这里我们借鉴数学归纳法的三个步骤:
1、初始状态
2、假设第j位以及第j位之前的我们都填完了
3、推论第j+1位该怎么填
next[j] == k;
next[k] == 绿色色块所在的索引;
next[绿色色块所在的索引] == 黄色色块所在的索引;
这里要做一个说明:图上的色块大小是一样的(没骗我?好吧,请忽略色块大小,色块只是代表数组中的一位)。
我们来看下面一个图,可以得到更多的信息:
1.由"next[j] == k;"这个条件,我们可以得到A1子串 == A2子串(根据next数组的定义,前后缀那个)。
2.由"next[k] == 绿色色块所在的索引;"这个条件,我们可以得到B1子串 == B2子串。
3.由"next[绿色色块所在的索引] == 黄色色块所在的索引;"这个条件,我们可以得到C1子串 == C2子串。
4.由1和2(A1 == A2,B1 == B2)可以得到B1 == B2 == B3。
5.由2和3(B1 == B2, C1 == C2)可以得到C1 == C2 == C3。
6.B2 == B3可以得到C3 == C4 == C1 == C2
上面这个就是很简单的几何数学,仔细看看都能看懂的。我这里用相同颜色的线段表示完全相同的子数组,方便观察。
接下来,我们开始用上面得到的条件来推导如果第j+1位失配时,我们应该填写next[j+1]为多少?
next[j+1]即是找str从0到j这个子串的最大前后缀:
我们已知A1 == A2,那么A1和A2分别往后增加一个字符后是否还相等呢?我们得分情况讨论:
(1)如果str[k] == str[j],很明显,我们的next[j+1]就直接等于k+1。
用代码来写就是next[++j] = ++k;
(2)如果str[k] != str[j],那么我们只能从已知的,除了A1,A2之外,最长的B1,B3这个前后缀来做文章了。(为什么B1,B3是最长的?假设它们不是最长的,还有更长的next[k]就等于更长的了)
那么B1和B3分别往后增加一个字符后是否还相等呢?
由于next[k] == 绿色色块所在的索引,我们先让k = next[k],把k挪到绿色色块的位置。如果还不相等就一直往前挪。挪到开头会做特殊处理。
由于j+1位之前的next数组我们都是假设已经求出来了的,因此,上面这个递归总会结束,从而得到next[j+1]的值。
我们唯一欠缺的就是初始条件了:
next[0] = -1, k = -1, j = 0
另外有个特殊情况是k为-1时,不能继续递归了,此时next[j+1]应该等于0,即把j回退到首位。
即 next[j+1] = 0; 也可以写成next[++j] = ++k
获得nexta的代码如下
//定义nexta[]大小与s相同
void getnexta(char s[])
{
memset(nexta,0,sizeof(nexta));
int n = strlen(s);
int k = -1,j = 0;
nexta[0] = -1;
while(j < n )
{
if(k == -1 || s[k] == s[j])
{
nexta[j + 1] = k + 1;
j ++;
k ++;
}
else
{
k = nexta[k];
}
}
}
下面谈谈
第二部分利用nexta数组进行匹配。
同样设两个指针i,j初始值都为零。假设j为模式串t的指针,i为母串s的指针。当s[i] ==t[j]时,显然i ++,j++.当j= -1时j要重新等于零,i也要向右移一位(因为这个位置已经与模式串全部试过了)除了这两种情况之外,剩余的状况及两个字符串不匹配且并未挪到开头之前,则j = nexta[j].
以下是kmp代码
int kmp(char s[],char t[])//t模式串,s母串.此种为返回首次匹配的位置,不能匹配则返回-1.
{
getnexta(t);
int n = strlen(s),m = strlen(t);
int i = 0,j = 0;
while(i < n && j < m)
{
if(j == -1 || s[i] == t[j])
{
i ++;
j ++;
}
else
{
j = nexta[j];
}
if(j == m)//根据题目要求改变
{
return i - j+ 1;
}
}
return -1;
}