接上一篇。
4、如何计算next数组
对于给定的字符串p,其next数组的含义是:对于k=next[j],p的前缀p[0…k-1]和p的后缀p[j-k…j-1]匹配,k要尽可能的大,且k<j.我们可以根据上述含义写出next的brute force计算代码。复杂度应该是O(n2)。
换个思路,现在next[0]=-1,next[1]=0.
假设k=next[j],则p[0…k-1]=p[j-k…j-1],那么求next[j+1]有两种情况:
1)如果p[k]=p[j],则p[0…k]=p[j-k…j],所以next[j+1]=k+1=next[j]+1;
2)如果p[k]!=p[j],这是可以看做另外一个字符串匹配的问题,主串和模式串都是p,当匹配失败时,k应该如何移动呢?显然是k=next[k]。请仔细琢磨这段话。【参考《数据结构-严蔚敏》p82-83】
仿照kmp算法,可以得到next数组的求法:
voidgetNext(const char *p,int *next)
{
int i,j;
next[0]=-1;
i=0;
j=-1;
while(i<(signed)strlen(p)-1)
{
if(j==-1||p[i]==p[j]) //匹配的情况下,p[j]==p[k]
{
i++;
j++;
next[i]=j;
}
else //p[j]!=p[k]
j=next[j];
}
}
next数组是kmp算法的核心,只有真正理解next数组,才能熟练掌握kmp算法。
复杂度分析:getNext()函数的复杂度是O(m),通常情况下模式串长度m<<主串长度n,因此增加的时间是值得的。由于使用了辅助数组,因此空间复杂度是O(m)。
虽然朴素字符串匹配算法的复杂度是O(n*m),但是在一般情况下实际的执行时间接近O(n+m)。KMP算法仅当模式串与主串之间存在许多“部分匹配”的情况下(这时才会有大量的回退)才显得比朴素匹配算法快得多。KMP算法的最大优点是不需要回溯指针,对主串仅需要从头至尾扫描一遍,对于外设输入庞大的文件很有效,可以边读入遍匹配,无需回头重读。
5、优化
其实上面的getNext()还可以优化:
voidgetNext(const char *p,int *next)
{
int i,j;
next[0]=-1;
i=0;
j=-1;
while(i<(signed)strlen(p)-1)
{
if(j==-1||p[i]==p[j]) //匹配的情况下,p[j]==p[k]
{
i++;
j++;
if(p[i]!=p[j]) next[i]=j;
else next[i]=next[j];///p[i]=p[j]时优化
}
else //p[j]!=p[k]
j=next[j];
}
}
这时匹配算法不变。
后记:kmp算法是D.E.Knuth与J.H.Morris与V.R.Pratt同时发现的字符串匹配的改良算法。也是我单个看的最久的算法。
在网上看很多blog,包括比较有名的matrix67,July等,但是我都没看太明白。尤其是July的,一大堆,前面的朴素算法都弄了一堆图,非常详细,这点很好。可是后面求next数组时,一会弄个下标从0开始的,一会弄个下标从1开始的,让新手无所适从。本来就是不懂才看,结果看完更郁闷了。最后还是看了严蔚敏老师的《数据结构》上的,才似乎懂了些。那本书上是下标从1开始的,我感觉讲的比网上的blog要详细的多,易懂的多。这本书之前大二时学习时没看太细,虽然考试分数挺高,但是显然书上好多内容都没有掌握。现在还在看。这里附上这本书的pdf连接,可以直接下载。
原创文章,转载请注明出处:http://blog.csdn.net/fastsort/article/details/9971953