KMP字符串匹配算法筆記

網上有不少解釋KMP算法的文章,A_B_C_ABC(見附件)的 這篇很詳細,反覆看了好幾遍,總算理解了個大概,可是總以爲沒那麼爽快。其實,一種算法各人有各人的理解方法,找到適合本身理解的才容易記住。下面是我對 這個算法的一些理解:

最普通的字符串匹配算法就不記了,簡單貼一下代碼
  1. int strstr ( char *sub, char* str ) {
  2.         int i= 0;
  3.         char *p=str, *q=sub;
  4.         while (* (p+i )!= '\0'&&* (q+i )!= '\0' ) {
  5.                 if (* (q+i )==* (p+i ) )
  6.                         i++;
  7.                 else {
  8.                         p++;
  9.                         i= 0;
  10.                 }
  11.         }       
  12.         if (* (q+i )== '\0' )
  13.                 return p-str;
  14.         return -1;
  15. }
 
下面說說我理解的KMP算法,與普通匹配算法不同的是,KMP算法在子串匹配失效的時候,下一步並非從新從子串的頭部開始匹配,而是根據一下 next函數計算出下一步應該從子串的什麼部位開始匹配。舉個例子更容易說明
紅色爲失效位置, '^'表示的當前是指針位置,'~'表示這次匹配A串開始的位置。
如果普通的匹配算法,當失效時,C串指針要回溯到頭部,A串指針也要回溯到'~'的下一位。也就是下一步應該是從A的第二字符(e.g. 'b')和C的開頭(e.g. 'a')開始匹配。如此循環
 
直到找到A串中的某一個位置能夠匹配C串。
然而從上面的匹配過程當中,能夠發現A和B的藍色部分是第一步中已經確認匹配過的,上面四步的匹配其實能夠看做是藍色部分的前半段與後半段在進行比 較,並且前三步在藍色部分就已經匹配失效了,因此這些比較是無謂的,由於它與父串A無關的,是屬於子串C自己的信息。只有第四步才涉及了與父串A中的字符 ('c')的比較
KMP算法正是利用這一點,它事先利用子串自己的信息就計算出當某一次匹配失效時,下一次應該繼續匹配的位置。也就是當C串在最後一個' b'匹配失效時,它省略了前三步(1,2,3)無謂的匹配,下一步比較 將在'd'與'c'之間進行。這樣父串A無需回溯,C串用一個next函數決定它將回溯的位置。因此next函數相當重要,也是這個算法的關鍵。
從上面的分析能夠知道next函數是子串C自己的性質決定的
假設子串 $P
next(j)=k(k>=0): 當P的第 j+1個字符 p_j匹配失敗時, 在父串指針不回溯的狀況下,下一步將與 p_{k+1}比較。next(j)的函數計算能夠表達爲
$$next(j)=  \left \{ \begin{array}{ll} maximum(k),   
when\hspace{1mm}0\leq k <j\hspace{1mm} and\hspace{1mm} p_0p_1....p_k =
 p_{j-k}p_{j-k+1}....p_j   & (1)\\ -1, otherwise  & (2) 
\end{array} \right.$$

當next(j)=k (k>=0)時,子串指針回溯到 p_{k+1}的位置,父串指針不變;
當next(j)=-1時,子串指針回溯到頭,父串指針前進一步;
在設計計算next值的程序的時候,咱們沒有必要每一步都去計算maximum(k),能夠用遞歸方式來作
舉個例子
假設子串爲P:"abacaba b", 且咱們將要求的是' b'的next值, e.g. next[7]
假設next[0~6]均爲已知: next[0]=-1, next[1]=-1 , next[2]=0 , next[3]=-1 , next[4]=0 , next[5]=1 ,next[6]=2
    " abac abab"
next[6]=2能夠說明P[0~2](藍)與P[4~6](紅)是同樣的
要求next[7]的值,咱們能夠找前6位("abacaba")中最長的前半段與後半段相等的子串,而後比較前半段子串的下一位是否和P[7]相 等。在這個例子中, P[0~next[6]](e.g. P[0~2])就是這樣的子串,接下來咱們比較 c 和 b也就是P[next[6]+1]('c')和P[7]('b').
  • 若相等則 next[7] = next[6]+1
  • 若不等, 咱們進一步找更短的前半段與後半段相等的子串,由於abaaba是同樣的, 要找'abacaba' 中比'abc'短的這樣的子串,至關於找'aba' 中的next[2]值是同樣的.也就是next[next[6]]的值。而後再比較P[next[next[6]]+1]和P[7]是否等,若不等則繼續 找更短的這樣子串

在上的例子中 P[next[6]+1]=P[3]('c') ,與P[7]('b')不相等, 可是  P[next[next[6]]+1] = P[next[2]+1] = P[1]('b'), 與P[7]('b')相等
最後能夠獲得next[7] = next[next[6]]+1 = next[2]+1= 1;
計算next值的代碼:
  1. void calnext ( char* P, int next [ ] ) {
  2.     next [ 0 ] = -1;           //第一個元素的next老是-1, 由於根據(1) , 咱們並找不到一個k比j=0小
  3.     for ( int i= 1; i<strlen (P ); i++ ) {
  4.         int k = next [i -1 ];     //由於採用遞歸的方法, 爲了計算next[i], 先記錄下next[i-1] 並且假設next[i-1]已知
  5.         while (P [k +1 ]!=P [i ]&&k>= 0 ) { // 遞歸
  6.             k = next [k ];
  7.         }
  8.         if (P [k +1 ]==P [i ] )     //若是相等而結束, 則找到一對長度爲k的前綴字串和後綴字串
  9.             next [i ] = k +1;       //增長了一個相同項
  10.         else
  11.             next [i ] = -1;          //其餘狀況
  12.     }
  13. }
匹配的代碼:
  1. int find ( char*T, char* pat ) {
  2.     int n = strlen (pat );
  3.     int *next = new int [n ];
  4.     calnet (pat, next );
  5.     char *p=T, *q=pat;
  6.     int i= 0;
  7.     while (*p!= '\0'&& (* (q+i )!= '\0' ) ) {
  8.         if (*p==* (q+i ) ) {
  9.             p++;
  10.             i++;
  11.         } else {
  12.             if (i== 0 )
  13.                 p++;
  14.             else
  15.                 i = next [i -1 ] +1;
  16.         }
  17.     }
  18.     if (* (q+i )== '\0' )
  19.         return p-T-n;
  20.     else
  21.         return -1;
  22. }
相關文章
相關標籤/搜索