在这里,不再对大名鼎鼎的KMP算法做过多赘述,如想了解可参考http://blog.csdn.net/v_JULY_v/article/details/6111565,写的很好!
首先,阐明一下这篇文章的目的:快速求解模式串的next以及nextval数组。当然这里面的模式串都比较短,在笔试或者面试的时候很有可能遇到,因此,如何快速求得是至关重要的!
废话不多说,我们直接切入主题,即关于next数组是如何求解的。而nextval数组是对next数组的改进,有这么个循序渐进的过程,所以先从next数组说起。我们都知道:next数组是表征模式串p自身的匹配程度的,所以我们先从一个串的前缀和后缀表达式说起。
串 | 前缀 | 后缀 | 最大长度 |
a | 无 | 无 | 0 |
aa | a | a | 1 |
ab | a | b | 0 |
aba | a,ab | a,ba | 1 |
abab | a,ab,aba | b,ab,bab | 2 |
ababa | a,ab,aba,abab | a,ba,aba,baba | 3 |
ababab | a,ab,aba,abab,ababa | b,ab,bab,abab,babab | 4 |
abababa | a,ab,aba,abab,ababa,ababab | a,ba,aba,baba,ababa,bababa | 5 |
aaaaaab | a,aa,aaa,aaaa,aaaaa,aaaaaa | b,ab,aab,aaab,aaaab,aaaaab | 0 |
以上表中串ababa为例:其前缀式有a,ab,aba,abab,而后缀式有a,ba,aba,baba,前缀与后缀匹配的串为:a,aba,因此串ababa的前缀与后缀的最大匹配长度为3。
好了,到这里可以直接来求next数组了!
这里,以模式串p=”ababab”为例吧!
1. next[0]=-1,这个应该没有疑问的,取-1是因为字符串都是从下标0开始索引的(当然,也可以取为0,这时字符串的下标就要从1开始了);
2. next[1]=?我们知道p[1]=b,p[1]之前的子串只有”a”,而字符串”a”的前缀与后缀最大匹配长度为0(上表第二行所示),因此,有next[1]=0;
3. next[2]=?从上一步的求解中可发现:欲求next[i],先找子串p[0~i-1]的前缀与后缀最大匹配长度,这也是与next数组的定义相吻合的next[k]=max{k|p[0~k-1]=p[i-k~i-1]},当然如果没有这样的k的话,就取next[k]=0,例如p[0~i-1]=”ab”时;因此,next[2]=0;
知道了如何求解next数组的方法,做起来就很容易了!
a b a b a b
-1 0 0 1 2 3
a a a a a a b
-1 0 1 2 3 4 5
a b a b b a b a c b a a b
-1 0 0 1 2 0 1 2 3 0 0 1 1
好了,到这里相信你已经可以独立的算出模式串的next数组了,下面,我们由next数组来推导nextval数组!
首先,对next数组做一个延伸,即多求一位next[i+1]:
a b a b a b
-1 0 0 1 2 3 4
a a a a a a b
-1 0 1 2 3 4 5 0
a b a b b a b a c b a a b
-1 0 0 1 2 0 1 2 3 0 0 1 1 2
注意上文中的红色数字,它们是由整个模式串的前缀后缀的最大匹配长度得来的。
a b a b a b
-1 0 0 1 2 3 4
-1 0 -1 0 -1 0
a a a a a a b
-1 0 1 2 3 4 5 0
-1 -1 -1 -1 -1 -1 5
a b a b b a b a c b a a b
-1 0 0 1 2 0 1 2 3 0 0 1 1 2
-1 0 -1 0 2 -1 0 -1 3 0 -1 1 0
蓝色字体的就是nextval数组,不知道大家有没有看出一些规律来?
首先,还是从nextval对next的改进之处说起吧!
void getnext(char p[])
{
next[0]=-1;
int lenp=strlen(p);
int i=0,j=-1;
while(i!=lenp-1)
{
if(j==-1||p[i]==p[j])
{
++i;
++j;
next[i]=j;
}
else
j=next[j];
}
}
void getnextval(char p[])
{
nextval[0]=-1;
int i=0,j=-1,lenp=strlen(p);
while(i!=lenp-1)
{
if(j==-1||p[i]==p[j])
{
++i;
++j;
if(p[i]!=p[j])
nextval[i]=j;
else
nextval[i]=nextval[j];
}
else
j=nextval[j];
}
}
从上面两段代码,可发现nextval对next的改进之处在于当p[i]==p[j]时,nextval[i]=nextval[j],而不再是nextval[i]=j。
注意到这一点之后,我们首先给出一个结论:当p[i]==p[j]时,必然有next[i+1]=next[i]+1(注意这里是next数组而不是nextval数组)。
来分析一下:
假定next[i]=k,则必有
k>=1:p[0~k-1]=p[i-k~i-1],j=k;若p[i]==p[j],—>p[0~k]=p[i-k~i] —>next[i+1]=k+1=next[i]+1;
k==0:p[0]!=p[i-1],j=0;若p[i]==p[j],—>p[0]=p[i] —>next[i+1]=1=next[i]+1;
好的,下面我们来见证一下如何从next数组来得到nextval数组:
若next[i+1]=next[i]+1,则nextval[i]=nextval[next[i]],0<=i<=strlen(p)-1;
对的,就是这么简单!
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
模式串 | a | b | a | b | b | a | b | a | c | b | a | a | b |
|
next[] | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 0 | 0 | 1 | 1 | 2 |
nextval[] | -1 | 0 | -1 | 0 | 2 | -1 | 0 | -1 | 3 | 0 | -1 | 1 | 0 |
上表中彩色部分一目了然!
至此,相信你已经可以快速完成next、nextval数组的求解了!