kmp的精华在于next数组,该数组存储了当子串与主串发生不匹配时应该调整的下标位置。对于next数组,直观来说就是当发生不匹配时,已经匹配的部分串里的前缀后缀的最大公共部分。以“abababca”为例,若在字符c处未匹配,已匹配部分ababab的前缀后缀最大公共部分为4,即abab;若在第二个b处发生不匹配,则最大公共部分为1,即a。若在第一个b处发生不匹配,则最大公共部分为0(最大公共部分不能是自身全长)。如何计算这个next数组的值呢?当然可以用循环的方式求取最大公共部分,如下:
int PreSuf(char a[],int b)//find maximum co-length of prefix and suffix in a string/array.
{
int i,j,length=0;
if(b==1)length=0;
for(i=1;i<b;i++)
{
for(j=0;j<i;j++)
{
if(a[j]!=a[b-i+j])break;
}
if(j==i&&i>length)length=i;
}
return length;
}
但这不是最好的方式,因为这样并未有效利用子串自身的信息。实际上,next数组每个元素之间具有一定的关系。
仍以abababca为例,因为在aba中已知a是公共部分,此时若下一个字符(第四个)为b,则正好与第二个字符匹配,这样公共部分正好从a变为ab,这就是next[i+1]=next[i]+1的意义;当然,下一个字符也可能和已知串的前缀部分(公共部分)的后一个字符不匹配,比如ababab,公共部分为abab,下一个字符为c,而公共部分为abab,下一个字符为a,ac就不同。此时应该用回溯法找到公共部分abab的公共部分–ab,考察ab的下一个和c是否一致,显然,也不一致,继续回溯,直到回溯到-1,说明公共部分为0,记next[i]=0.这里请自己画图仔细理解:
1、任何一个公共部分,一方面他是作为一个子串的前后缀公共部分,另一反面,他自己本身内部又有公共部分,是一层套一层的关系。abab作为ababab的公共部分,同时内部也有公共部分–ab。
2、如1中所述,ab是abab的前缀和后缀,而abab是ababab的前缀和后缀,所以,ab同时是ababab的前缀的前缀,前缀的后缀,后缀的前缀,后缀的后缀。这里我们只需要考虑前缀的前缀,后缀的后缀,他们相等。正是这一个关系,才让回溯有了理由。
代码如下:
int Next(Str str,int next[])
{
int i,j=0;
next[0]=-1;
next[1]=0;
for(i=2;i<str.length;i++)
{
while(j&&str.ch[j]!=str.ch[i-1])j=next[j];
if(str.ch[j]==str.ch[i-1])j=j+1;
next[i]=j;
}
}
全部kmp代码如下:
int Kmp(Str str1,Str str2,int next[])
{
int i=0,j=0;
while(i<str1.length&&j<str2.length)
{
if(str1.ch[i]==str2.ch[j])
{
i++;
j++;
}
else
{
j=next[j];
if(j=-1)
{
j=0;
i++;
}
}
}
if(j==str2.length)return i-str2.length;
else return -1;
}