EXKMP解决的问题
定义母串S和子串T,S的长度为n,T的长度m。
求字符串T和字符串S的每一个后缀的最长公共前缀,这就是EXKMP解决的问题,保存这个数据的数组就是extend数组,而exkmp就是求出extend数组。
(如果extend[i] = m,则表示T串为S的子串之一,这也就是标准的kmp问题,所以称其为拓展kmp)。
与kmp区别
虽然叫做拓展kmp,并且和kmp一样都有next数组,但二者实际上并没有太大的关系。kmp的next[i] 表示p串中 0~i-1这个前缀的最长相同前后缀,例如abcabc的next[3]也就是abc这个前缀的最长相同的前后缀。而exkmp的next[i] 表示的是字串T[i, m-1]和T的最长公共前缀长度。
extend求法
首先举一个例子 S = “aaaabaa”, T = “aaaaa”。
计算extend[0]的时候,进行五次匹配,一直到失配后停止。如图(后缀包括串本身,别懵啊,要时刻记住exkmp是求S的所有后缀和T的最长公共前缀)
得到extend[0] = 4, 然后计算extend[1],那么计算extend[1]的时候我们肯定不能像求extend[0]那样去一点一点的匹配,那就和暴力没什么区别了。
我们知道extend[0] = 4, 容易知道S[0, 3] == T[0, 3],在进一步就能得到S[1, 3] == T[1, 3]。而计算extend[1]是从字符串1开始的,现在我们至少已经知道了S[1, 3]和T[1, 3]是相等的,通过上面我们又知道next数组的含义,现在的next[1]等于4,表明了T[0, 3]==T[1, 4], 那也就是说T[0, 2] = =T[1, 3],回到上面加粗的地方我们就知道了,S[1, 3] == T[0, 2],这样的话S[1, 3]和T[0, 2]是不需要再次匹配,直接往下匹配S[4] 和 T[3]就行。
那上面一定都要继续往下匹配吗?答案是否定的,在这里可以分成了两种情况。为什么请继续往下看。
如果某一刻extend[0, k]都已经计算完了,现在需要计算extend[k+1], 并且在extend[0, k]的最大值的坐标为po, 到达最远的距离为p。如下图,
计算extend[k+1],根据上面推论可知S[Po, p] = T[0, P-Po],因此S[k+1, p] = T[K-Po+1, P-Po],令len = next[K-Po+1](next[i]表示T[i,m-1]和T的最长公共前缀长度,请牢牢地记住它的含义,这对理解后面很有用处) 分为下面两种情况
1),k+len < p
如下图
这种情况不需要任何匹配,仔细想一下就能知道extend[k+len+1]一定不等于Tp[len+1],因为如果二者相等了,说明刚才的next[K-Po+1]一定是等于len+1, 这就形成与已知相悖。因此extend[k+1]直接能够赋值为len。(有一点绕,一定要对着图好好想一想。而且要记住是从Po到P和k没什么关系)。
2),k+len>=p
如下图,
通过图片我们可以知道S[k+1, P] == T[0, p-k]是相等的,但是S[P+1]的情况是未知的,因此还要从S[P+1]和T[p-k+1]开始一点一的匹配,知道失配位置,这个时候的最远距离P已经改变,一定要更新P和Po,好为后面计算做铺垫。
next数组求法
next[i]表示T[i,m-1]和T的最长公共前缀长度。根据它的定义,我们发现它和extend数组没有什么区别啊,这里只不过是S和T是一个串,那这里说明了求两个数组用的是一种方法, 不过有一点小小的不同。在代码里看吧。
代码样例
#include <iostream>
#include <cstring>
using namespace std;
const int MAX=100010; //字符串长度最大值
int Next[MAX],extend[MAX];
//预处理计算Next数组
void getNext(char str[])
{
int k=0,j,po,len=strlen(str);
Next[0]=len; //初始化next[0]
while(str[k]==str[k+1] && k+1<len) k++;
Next[1]=k; //计算next[1]
po=1; //初始化po的位置
for(k=2;k<len;k++)
{
if(Next[k-po]+k < Next[po]+po) //第一种情况,可以直接得到next[k]的值
Next[k]=Next[k-po];
else //第二种情况,要继续匹配才能得到next[k]的值
{
j = Next[po]+po-k;
if(j<0) j=0; //如果i>po+next[po],则要从头开始匹配
while(k+j<len && str[j]==str[j+k]) j++; Next[k]=j;
po=k; //更新po的位置
}
}
}
//计算extend数组
void EXKMP(char s1[],char s2[])
{
int k=0,j,po,len=strlen(s1),l2=strlen(s2);
getNext(s2); //计算子串的next数组
while(s1[k]==s2[k] && k<l2 && k<len) k++;
extend[0]=k;
po=0; //初始化po的位置
for(k=1;k<len;k++)
{
if(Next[k-po]+k < extend[po]+po) //第一种情况,直接可以得到extend[k]的值
extend[k]=Next[k-po];
else //第二种情况,要继续匹配才能得到extend[k]的值
{
j = extend[po]+po-k;
if(j<0) j=0; //如果k>extend[po]+po则要从头开始匹配
while(k+j<len && j<l2 && s1[j+k]==s2[j]) j++; extend[k]=j;
po=k; //更新po的位置
}
}
}