KMP算法的关键之处在于next数组的理解!
假设t为目标,p为模式。j为t中下标,i为p中下标。
先从朴素模式匹配算法理解下或许会好些:
当模式串中的p[i]与目标串中的t[j]不等时,需要把目标中的指针回朔到j-i+1处重新用t[j-i+1]与p[0]匹配。在目标指针回朔的过程中,就可能导致许多不必要的重复匹配。
KMP算法的思想是:当p[i]!=t[j]时,要能找到模式串一个大于等于1的右移位数(在朴素模式下总是右移1位),并且确定p和t继续比较的字符。最为理想的情况就是,希望匹配过程对于t是无回朔的。也就是说在,在(模式串)右移若干位后,应该立即用p中一个新的字符p[k](显然k<i)和t[j](甚至t[j+1])继续比较。
经过前辈们的研究发现这个k值不仅存在,而且仅依赖于模式串p本身(也就是说一旦模式串p一确定,对于每个字符就有不同的固定的k值),与目标串t无关。因此可以在与任何目标串匹配前计算出来。
先来看看这个k值的存在性:当p[i]!=t[j]时,假设在p[i]前有匹配的字符,即i>0(如果没有匹配的字符,即i=0,此时目标串指针肯定不用回朔,直接用t[j+1]和p[0]比较),此时,p[0]~p[i-1]和t[j-i]~t[j-1]匹配。这就给我们提供了一种思路,可以通过分析p[0]~p[i-1]来分析t[j-i]~t[j-1]。
如果模式串中存在这样一个k值,使得p[0]~p[k-1]=p[i-k]~p[i-1](共k个匹配字符,且k是满足这个关系的最大值),则可以知道p[0]~p[k-1]和t[j-k]~t[j-1]匹配。因此可以直接把模式串右移i-k位,使得p[0]~p[k-1]和t[j-k]~t[j-1]匹配,直接比较p[k]和t[j](当右移量小于i-k时,也用不着比较,因为根据k的意义,此时用长度大于k的前后缀对齐,比较结果必定不等;当右移量大于i-k时,又可能错过目前匹配成功的机会)。此时next[i]=k;
如果不存在这样的k值,也就是说p[0]开始的任何字串不等于p[i-1]结尾的任何字串。基于p[0]~p[i-1]和t[j-i]~t[j-1]匹配的事实,可以说p[0]开始的任何字串不等于t[j-1]结尾的任何字串,这说明主串没有必要回朔到t[j-i]~t[j-1],既然没有必要回朔并且p[i]!=t[j],则t[j]直接与p[0]比较。此时next[i]=0。
特殊情况,i=0时,p[i]=p[0]!=t[j],则变成p[0]和t[j+1]比较。为了统一令next[0]=-1,所以在kmp的算法设计中,判断到i=-1时,可以用i++,j++使得i=0,j=j+1形成p[0]与t[j+1]比较的形式。
理解了next数组,理解kmp算法就轻而易举了。
kmp算法:
{
int i,j;
i=0;j=0;
while(i<p->n&&j<t->n)
if(i==-1||p->c[i]==t->c[j])
{i++;j++;}
else i=next[i];
if(i>=p->n)
return(j-p->n+1);
else return(0);
}
再来看看计算next数组的算法:
从上面的推导,next[0]=-1;
当next[i]=k(说明p[0]~p[k-1]=p[i-k]~p[i-1])时,求next[i+1]=?(k<i)
当p[i]=p[k]时,说明p[0]~p[k-1]p[k]=p[i-k]~p[i-1]p[i],则next[i+1]=k+1;
当p[i]!=p[k]时,则说明p[0]~p[k-1]p[k]!=p[i-k]~p[i-1]p[i],显然这又是一个模式匹配问题(p同时为模式串和目标串),从kmp算法看此时应该用p[next[k]](注意理解下这儿的next[k])与p[i]比较,使k=next[k](next[k]<k,k在逐渐递减),再用p[i]与p[k]比较直到相等或者k=-1为止。如果k=-1,表示没有k使p[0]开始的任何字串等于p[i]结尾的任何字串,所以直接用p[i]与p[0]比较,则next[i+1]=0=-1+1。如果某个k使p[i]=p[k],则next[i+1]=k+1(注意此时的k不等于一开始的k,因为k在逐渐递减)。所以next[i+1]=k+1。再次注意,k已经变化。
next的算法:
{
int i=0,k=-1;
next[0]=-1;
while(i<p->n-1)
{
while(k>=0&&p->c[i]!=p->c[k]) k=next[k];
i++;j++;
next[i]=k;
}
}