聲明原文連結:http://www.inf.fh-flensburg.de/lang/algorithmen/pattern/kmpen.htm ;原文的“text”翻譯為主串,通常用S表示,長度為n;原文的"pattern"翻譯為模式串,通常用T表示,長度為m;括弧中是自己的一些注釋;思想在移動了模式串之後,原始的演算法(應該就是指普通的BF吧~)忽略了之前匹配的資訊,所以可能造成主串一遍又一遍的回溯,最壞情況下的複雜度為Θ(nm)
。KMP演算法利用了從之前的匹配中獲得的資訊,它不會重複比較匹配過的主串(也就是S的指標永遠不會回溯),所以它的複雜度為O(n)。無論如何,分析模式串的結構是非常必要的。這個過程的複雜度是O(m),而 mn
,所以整個演算法的複雜度為O(n)。基本定義
定義:(首碼尾碼不再羅嗦了。。就是不包括同 x 串完全相等的,翻譯為真首碼和真尾碼)x串的border(不知道怎麼翻譯,保留),是這樣一個子串r,使得r = x
0 ... xb-1並且 r = xk-b ... xk-1 , b {0, ..., k-1}
(其實就是串 x 的前面一部分和後面一部分是相等的,相等部分的子串的長度是b,想想為什麼定義這個border,很好的思想哦~這樣主串的指標就不必回溯了~後面會說明~)x串的border即是真首碼,也是真尾碼,我們將b稱為x的border的長度。
比如:讓 x = “abacab”
真首碼:ε, a, ab, aba, abac, abaca
真尾碼:ε, b, ab, cab, acab, bacab
border:ε(長度是0), ab(長度是2)
空串 ε 總是x的border,而它自己沒有border。下面的例子說明了KMP演算法是怎樣根據一個串的border來決定移動的距離的。(說是移動的距離,體現在程式中,其實就是模式串的指標 j 要向前怎麼移動才能保證主串S的指標不回溯就能繼續判斷匹配)
例子:0 1 2 3 4 5 6 7 8 9 ...
a b c a b c a b d
a b c a b d
a b c a b d
可以看出0到4都匹配了,5沒有匹配,模式串可以移動三個位置(即不必判斷模式串的前兩位,指標 j 移動到第三位 c 的位置),主串的位置仍然保留在5。移動的距離是根據最長相符的首碼得到的,是該首碼的border長度的最大值。在這個例子中,最長相符首碼是abcab,它的長度是 j = 5.他最長的border是ab,長度是2,所以移動的距離是 j - b = 5 - 2 = 3.在這個過程中,每個模式串的首碼的最長的border值都可以得到。然後在尋找的過程中,移動距離就可以根據匹配的首碼計算出來了。過程理論: 讓r, s作為串 x 的border,並且滿足|r| < |s|,那麼r一定是s的border.證明: 展示了r,s作為兩個border 的串 x 。r是x的首碼,也是真首碼,因為它比s短。而r也是x的尾碼,因此,也是s的真尾碼。所以r是串s的border。 定義:讓 x 作為一個串, a 作為一個字母,如果ra是xa的border,那麼x的border就可以從
r 擴充到 ra(過程演算法的理論依據哦~稱為T1.1)。展示了這個過程: 在這個過程中,一個m+1為長度的數組會被計算出來。b[i]表示模式串(i = 0, ..., m)中長度為i的首碼的最長border的長度。由於空的首碼沒有border,所以我們將b[0] 置為-1。是擁有長度為b[i]的border的模式串的首碼。 假設b[0], ..., b[i] 的值都是已知的,b[i+1] 的值可以通過檢查首碼p0 ... pi-1 的border是否可以由pi擴充。這就是 pb[i] = pi 的情況,如。那麼borders就可以通過前面 b[i], b[b[i]] 等等的值計算出來。這個過程演算法包含一個 j 的迴圈。如果pj = pi 的話,長度為 j 的border又可以被 pi 擴充,否則pj <> pi的話,下一個最長的border 就能通過讓 j = b[j] 獲得。這個迴圈結束的條件是沒有border可以被擴充(j = -1)。在 j++語句之後,這個值就被寫入b[i+1]。(也就是說,根據T1.1,如果pj = pi,擴充後b[i+1] 的值就是b[i] 的值+1即可;但如果不相等的話,令j = b[j] ,再判斷新的 pj 與 pi 是否相等,依此類推,也就是程式中的while迴圈!因為不相等時,border的長度肯定更短,而b[j] 就是首碼p0, p1,...,pj-1的border,因為前後部分一樣,下面在此判斷pi 和 pj 是否相等又是在重複前面的過程,如果相等還是根據T1.1加一得到b[i+1]的值,這點不太好想,有點遞迴的趕腳。。)過程演算法:
void kmpPreprocess(){ int i=0, j=-1; b[i]=j; while (i<m) { while (j>=0 && p[i]!=p[j]) j=b[j]; i++; j++; b[i]=j; }}例如:對於模式串 p = ababaa,數組b中的border的長度有下面的值。比如我們有 b[5] = 3,因為長度為5的首碼 ababa 擁有長度為3的border.
尋找演算法上面的過程演算法可以被應用到串pt 上來。如果只有最長的borders被計算出來,那麼pt 的首碼的長度為m的border會匹配模式串 t ,假設border不是自我重疊的,如所示:
這解釋了過程演算法和下面的尋找演算法的相似之處。尋找演算法:
void kmpSearch(){ int i=0, j=0; while (i<n) { while (j>=0 && t[i]!=p[j]) j=b[j]; i++; j++; if (j==m) { report(i-j); j=b[j]; } }}在內層迴圈中,在 j 處不匹配的時候,匹配模式串的首碼中最長的border會被考慮,如:
繼續在b[j]處比較,border的長度會導致模式串的移動。如果發生不匹配的情況,考慮下一個最長border,以此類推,直到沒有剩下的border,即j = -1 或者下一個符號匹配了。然後我們就有了新的模式串的匹配首碼,可以繼續進行外層迴圈了。
如果模式串的所有m個符號都和主串匹配完畢,即j = m,會調用一個函數來返回匹配的位置i-j。然後,模式串會在最長border允許的範圍內儘可能多的移動。
下面的例子示範了尋找演算法,綠色代表匹配,紅色代表不匹配。
例子:
分析過程演算法內層的 while 迴圈將 j 的值減少到至少為1,因為b[j] < j. 迴圈在 j = -1的時候停止,因此,它最多是和 j++ 的次數相等。由於j++ 在外層迴圈中執行了m次,所以內層迴圈的整體次數最多是m,所以過程演算法需要O(m) 步。相似的尋找演算法需要O(n)步。上面的例子展示了這個“階梯狀的圖”的寬度至少和高度相等(因為每次不匹配都會移動,最極端的情況是每一次都移動1,此時寬度和高度相同),因為最多有2n次比較。
所以KMP演算法的複雜度為O(n).其他模式值其實next 函數也有其他的定義,但思想都是利用前面的比較來消除回溯提高效率,參考連結 : http://www.cppblog.com/oosky/archive/2006/07/06/9486.html