接上一篇。
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