KMP算法的實現以及改進

這兩天看了一下KMP算法,它是什麼,我就不贅述了。不懂的自己動手查查。

 

我已經把代碼上傳到Github了,可以去那裏下載,地址如下:

https://github.com/nemax/KMP-search-algorithm

 

一般來說,我們習慣於把KMP和Brute-Force解法比較,那麼KMP到底勝在什麼地方呢?勝在它的覆蓋函數。什麼是覆蓋函數呢,它是一個用來計算模式串自身信息的函數,計算出來的函數表徵着自我覆蓋的函數,所以說它是覆蓋函數。(其實還聽過叫next函數或其他名字的。)

 

約定:紅色字母代表失配字母,綠色字母代表上一輪比較結束後,這一輪起始比較位置,而藍色代表起始位置和失配位置一樣。

 

簡單的來說明一下,假如有模式串abaabc,主串abaabacdad,第一輪匹配剛好最後一個c沒法匹配,如下:

主串:     a b a a b a c d a d

模式串:  a b a a b c

對於BF而言,是從頭再匹配了。但是想想,最後一個c沒法匹配就意味着前面的都對着呢。說明在主串中,對不上的地方的前兩個字符肯定是ab,而注意到沒有,模式串一開頭就是ab,如果知道這一點,我們是不是可以把這兩次匹配省了,直接從上面的情形跳到下面這樣,從第三個a開始比較

主串:     a b a a b a c d a d

模式串:          a b a a b c

而對於BF而言,在上一步錯了以後應該是下面這樣

主串:     a b a a b a c d a d

模式串:    a b a a b c

 

發現差異有多大了嗎?看看起始位置,KMP可以避免主串指針的回溯,而BF法一旦一輪結束,必須指針回溯。這就是二者差異所在,KMP充分利用了模式串自身的信息,避免了指針回溯,避免了不必要的比較。在KMP算法中,模式串的每一個字符都有自己相應的next值,何謂next值,可以膚淺地理解爲在下標爲i的字符處失配時,我們下次應該用模式串中下標爲next[i]的字符來比對。而計算next值得函數就是前面說道的覆蓋函數。

 

我大概看過兩種計算next值得方法,一種計算出來的next值直接告訴你失配後下一次用哪個位置的字符比較,而另一種給出的next值需要經過一個固定的計算,算出一下次需要比對的位置。而KMP的核心,也就在於這個next值得計算。

 

跟着next函數走一遍,你會發現,代碼其實很簡單。難者不會,會者不難。

首先他有兩個指針,一個指向當前要計算的字符,即指針k,另一個指針value不好解釋,看着就知道了。

 

我們先簡單說一下next值怎麼算吧,先說第一種,對應代碼如下:

 

  1. void get_next_array_origin(char * pattern){ 
  2.  
  3.     //Get pattern length 
  4.  
  5.     int length = strlen(pattern); 
  6.  
  7.  
  8.  
  9.     int k = 1,value=0; 
  10.  
  11.     //It`s a rule to set next[0] with value -1 
  12.  
  13.     next[0]=-1; 
  14.  
  15.  
  16.  
  17.     while(k < length){ 
  18.  
  19.         //Keep the next value of last unmatch character 
  20.  
  21.         value = next[k-1]; 
  22.  
  23.         /*value>=0 means there is some overlays was discovered, 
  24.  
  25.           the second condition means the overlay was stop here 
  26.  
  27.         */ 
  28.  
  29.         while(value >= 0 && pattern[k] != pattern[value+1]){ 
  30.  
  31.             value = next[value]; 
  32.  
  33.         } 
  34.  
  35.         /*It means we discoverd an overlay and pattern[k] is the 
  36.  
  37.           first(value equals -1) or subsequent char(value >= 0). 
  38.  
  39.         */ 
  40.  
  41.         if(pattern[k] == pattern[value+1]){ 
  42.  
  43.             next[k] = value+1; 
  44.  
  45.         } 
  46.  
  47.         //Other condition 
  48.  
  49.         else
  50.  
  51.             next[k]=-1; 
  52.  
  53.         } 
  54.  
  55.         k++; 
  56.  
  57.         //printf("next[%d] = %d\n",k-1,next[k-1]); 
  58.  
  59.     } 
  60.  

假設模式串被表示爲a[0]a[1]..a[k]...a[j-k]...a[j],如果把a[0]到a[k]和a[j-k]到a[j]剛好能配得上,那麼next[j] = k,也可以說發現了覆蓋,即模式串自身內部的重疊.如果找不到這樣的匹配,next[j]=-1特殊的地方就在於next[0]=-1是定死的,無論哪種覆蓋函數。

這下好辦了吧,根據這個定義看看abaabc是多少?

1.next[0]=-1;

2.只看ab,next[1]=-1;

3.只看aba,next[2]=0,因爲next[0]到next[0]和next[2-0]到next[2]一樣,所以next[2]=0;

4.只看abaa,next[3]=0,因爲next[0]到next[0]和next[3-0]到next[3]一樣,所以next[3]=0;

5.只看abaab,next[5]=1,因爲next[0]到next[1]和next[5-1]到next[5]一樣,所以next[5]=1;

6.同理,next[6]=-1

所以,

模式串:a  b a a b c

next值:-1 -1 0 0 1 -1

那麼這個值怎麼用呢,這樣的規則計算出來的next值就如我前面說的那樣,不是直接告訴你失配了再比較哪一個,而是要計算的。

舉個例子,加入c失配了,我們知道主串失配處前兩個字符爲ab,我們的模式串一開始也爲ab,所以,我們用模式串的next[2]處的a來比較就好了,因爲模式串前面那兩個ab,肯定和主串中失配位置前那兩個ab重合。那這個next[2]的2是怎樣計算出來的呢,很遺憾,我們用的不是c對應的next值,而是c之前一位的next值,也就是甚爲next[4]的b的next值加一計算出來的。所以,計算公式就是:

下一個比較的字符的下標 = 模式串中最後一個匹配得上的字符的next值+1

然而,我們有更好地next值計算方法。代碼如下:

 

  1. void get_next_array(char * pattern){ 
  2.  
  3.     int length = strlen(pattern) ; 
  4.  
  5.     int k = 0,value=-1; 
  6.  
  7.     next[k]=-1; 
  8.  
  9.  
  10.  
  11.     while(k < length){ 
  12.  
  13.         while(value >= 0 && pattern[k] != pattern[value]){ 
  14.  
  15.             value = next[value]; 
  16.  
  17.         } 
  18.  
  19.  
  20.  
  21.         k++; 
  22.  
  23.         value++; 
  24.  
  25.         next[k] = value; 
  26.  
  27.     } 
  28.  

next[0]=-1還是不變,指針還是兩個,不過,其他計算過程稍有不同。這樣計算出來的值就直接告訴你如果匹配錯了,下一次用下標爲幾的字符匹配。next值具體計算過程看代碼。

模式串:a b a a b c

next值:-1 0 0 1 1 2

如果在c處失配,則下一個用pattern[c的next值],即pattern[2]來匹配。

 

其實理解透徹了以後你會發現,value的值其實是向前推進的,如果有覆蓋的話,而如果沒有覆蓋,它會往前回溯到前一個可能發生或延續覆蓋的地方,如果一直沒法發生或延續覆蓋,它最終退爲-1。

 

其實就和前面說的那個好多個a的公式有點相似了。

 

其實還有辦法改進這個next值得算法,想想看,還是上面那個串,假如在next[2]處的a失效了是不是下一次應該比較next[0]的值,而next[0]還是一個a,肯定不匹配,最終主串指針進一,模式串從頭匹配。所以我們是不是可以再改進一下next值,省去了這樣的盲目跳轉,改進的算法對應get_next_array_enhanced()。這樣計算出來以後使用方法和第二種差不多,但是如果失配位的next值爲-1就直接做主串指針進一,從頭匹配的操作。

 

所有的代碼如下:

KMP.h

  1. #include <string.h> 
  2.  
  3. #include <stdio.h> 
  4.  
  5.  
  6.  
  7. static int next[20]={0}; 
  8.  
  9.  
  10.  
  11. /*This is the worst one I think,the next value dosen`t tell 
  12.  
  13.   you where the pattern_index should be put then,but it can 
  14.  
  15.   work out by the value. 
  16.  
  17. */ 
  18.  
  19. void get_next_array_origin(char * pattern){ 
  20.  
  21.     //Get pattern length 
  22.  
  23.     int length = strlen(pattern); 
  24.  
  25.  
  26.  
  27.     int k = 1,value=0
  28.  
  29.     //It`s a rule to set next[0] with value -1 
  30.  
  31.     next[0]=-1; 
  32.  
  33.  
  34.  
  35.     while(k < length){ 
  36.  
  37.         //Keep the next value of last unmatch character 
  38.  
  39.         value = next[k-1]; 
  40.  
  41.         /*value>=0 means there is some overlays was discovered, 
  42.  
  43.           the second condition means the overlay was stop here 
  44.  
  45.         */ 
  46.  
  47.         while(value >= 0 && pattern[k] != pattern[value+1]){ 
  48.  
  49.             value = next[value]; 
  50.  
  51.         } 
  52.  
  53.         /*It means we discoverd an overlay and pattern[k] is the 
  54.  
  55.           first(value equals -1) or subsequent char(value >= 0). 
  56.  
  57.         */ 
  58.  
  59.         if(pattern[k] == pattern[value+1]){ 
  60.  
  61.             next[k] = value+1; 
  62.  
  63.         } 
  64.  
  65.         //Other condition 
  66.  
  67.         else{ 
  68.  
  69.             next[k]=-1; 
  70.  
  71.         } 
  72.  
  73.         k++; 
  74.  
  75.         //printf("next[%d] = %d\n",k-1,next[k-1]); 
  76.  
  77.     } 
  78.  
  79.  
  80.  
  81.  
  82. /*This is the second next value caculate algorithm,it`s  
  83.  
  84.   convenient.Because the next value tell you what the   
  85.  
  86.   next value of pattern_index. 
  87.  
  88. */ 
  89.  
  90.  
  91.  
  92. void get_next_array(char * pattern){ 
  93.  
  94.     int length = strlen(pattern) ; 
  95.  
  96.     int k = 0,value=-1; 
  97.  
  98.     next[k]=-1; 
  99.  
  100.  
  101.  
  102.     while(k < length){ 
  103.  
  104.         while(value >= 0 && pattern[k] != pattern[value]){ 
  105.  
  106.             value = next[value]; 
  107.  
  108.         } 
  109.  
  110.  
  111.  
  112.         k++; 
  113.  
  114.         value++; 
  115.  
  116.         next[k] = value; 
  117.  
  118.     } 
  119.  
  120.  
  121.  
  122.  
  123. /*It`s an improvement algrithm for get_next_array().Former just 
  124.  
  125.   tell you where to set you pattern_index,but not to concerned  
  126.  
  127.   about is the next char equal to the mismatch one,this algori- 
  128.  
  129.   thm fix this problem. 
  130.  
  131. */ 
  132.  
  133. void get_next_array_enhanced(char * pattern){ 
  134.  
  135.     int length = strlen(pattern) ; 
  136.  
  137.     int k = 0,value = -1; 
  138.  
  139.     next[k]=value; 
  140.  
  141.  
  142.  
  143.     while(k < length){ 
  144.  
  145.         while(value>=0 && pattern[k]!=pattern[value]){ 
  146.  
  147.             value = next[value]; 
  148.  
  149.         } 
  150.  
  151.         /*Once the next char is equal to the current one,also 
  152.  
  153.           the mismatch one,we do this.Although they are equal 
  154.  
  155.           characters,but the next value of former one has been 
  156.  
  157.           work out,so it`s an available next value for the seond 
  158.  
  159.           one.  
  160.  
  161.         */ 
  162.  
  163.         if(pattern[k] == pattern[value]){ 
  164.  
  165.             next[k] = next[value]; 
  166.  
  167.         } 
  168.  
  169.         k++; 
  170.  
  171.         value++; 
  172.  
  173.         next[k] = value; 
  174.  
  175.     } 
  176.  
  177.  
  178.  
  179.  
  180. void KMP_search_origin(char * main,char * pattern){ 
  181.  
  182.     get_next_array_origin(pattern); 
  183.  
  184.     int main_index = 0,pattern_index = 0
  185.  
  186.     int main_length = strlen(main); 
  187.  
  188.     int pattern_length = strlen(pattern); 
  189.  
  190.     int flag=-1; 
  191.  
  192.     while(main_index<main_length){ 
  193.  
  194.         //printf("main_index:%d\n",main_index); 
  195.  
  196.         if(main[main_index] == pattern[pattern_index]){ 
  197.  
  198.             //printf("%c = %c\n",main[main_index],pattern[pattern_index]); 
  199.  
  200.             if(pattern_index == pattern_length-1){ 
  201.  
  202.                 printf("find in place %d\n",main_index - pattern_length+1); 
  203.  
  204.                 flag=1
  205.  
  206.                 /*Once the last char equals the first char in pattern, 
  207.  
  208.                   that means current char in main string can match the 
  209.  
  210.                   first char in pattern,so we do this to avoiding miss 
  211.  
  212.                   the comparision. 
  213.  
  214.                 */ 
  215.  
  216.                 if(pattern[0] == pattern[pattern_index]){ 
  217.  
  218.                     main_index--; 
  219.  
  220.                 } 
  221.  
  222.                 pattern_index = -1; 
  223.  
  224.             } 
  225.  
  226.             main_index++; 
  227.  
  228.             pattern_index++; 
  229.  
  230.         } 
  231.  
  232.         else{ 
  233.  
  234.             //printf("%c != %c\n",main[main_index],pattern[pattern_index]); 
  235.  
  236.             if(pattern_index == 0){ 
  237.  
  238.                 main_index++; 
  239.  
  240.             } 
  241.  
  242.             else{ 
  243.  
  244.                 /*caculate the next position to be compare according 
  245.  
  246.                   to the next value 
  247.  
  248.                 */ 
  249.  
  250.                 pattern_index = next[pattern_index-1]+1; 
  251.  
  252.             } 
  253.  
  254.         } 
  255.  
  256.     } 
  257.  
  258.     if(flag == -1){ 
  259.  
  260.         printf("Sorry,we find nothing."); 
  261.  
  262.     } 
  263.  
  264.  
  265.  
  266.  
  267. void KMP_search(char * main,char * pattern){ 
  268.  
  269.     get_next_array(pattern); 
  270.  
  271.     int main_index = 0,pattern_index = 0
  272.  
  273.     int main_length = strlen(main); 
  274.  
  275.     int pattern_length = strlen(pattern); 
  276.  
  277.     int flag=-1; 
  278.  
  279.     while(main_index<main_length){ 
  280.  
  281.         //printf("main_index:%d\n",main_index); 
  282.  
  283.         if(main[main_index] == pattern[pattern_index]){ 
  284.  
  285.             //printf("%c = %c\n",main[main_index],pattern[pattern_index]); 
  286.  
  287.             if(pattern_index == pattern_length-1){ 
  288.  
  289.                 printf("find in place %d\n",main_index - pattern_length+1); 
  290.  
  291.                 flag=1
  292.  
  293.                 if(pattern[0] == pattern[pattern_index]){ 
  294.  
  295.                     main_index--; 
  296.  
  297.                 } 
  298.  
  299.                 pattern_index = -1; 
  300.  
  301.             } 
  302.  
  303.             main_index++; 
  304.  
  305.             pattern_index++; 
  306.  
  307.         } 
  308.  
  309.         else{ 
  310.  
  311.             //printf("%c != %c\n",main[main_index],pattern[pattern_index]); 
  312.  
  313.             if(pattern_index == 0){ 
  314.  
  315.                 main_index++; 
  316.  
  317.             } 
  318.  
  319.             else{ 
  320.  
  321.                 /*It`s easier than before,the next value is 
  322.  
  323.                   just where to put next time. 
  324.  
  325.                 */ 
  326.  
  327.                 pattern_index = next[pattern_index]; 
  328.  
  329.             } 
  330.  
  331.         } 
  332.  
  333.     } 
  334.  
  335.     if(flag == -1){ 
  336.  
  337.         printf("Sorry,we find nothing."); 
  338.  
  339.     } 
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346. void KMP_search_enhanced(char * main,char * pattern){ 
  347.  
  348.     get_next_array(pattern); 
  349.  
  350.     int main_index = 0,pattern_index = 0
  351.  
  352.     int main_length = strlen(main); 
  353.  
  354.     int pattern_length = strlen(pattern); 
  355.  
  356.     int flag=-1; 
  357.  
  358.     while(main_index<main_length){ 
  359.  
  360.         //printf("main_index:%d\n",main_index); 
  361.  
  362.         if(main[main_index] == pattern[pattern_index]){ 
  363.  
  364.             //printf("%c = %c\n",main[main_index],pattern[pattern_index]); 
  365.  
  366.             if(pattern_index == pattern_length-1){ 
  367.  
  368.                 printf("find in place %d\n",main_index - pattern_length+1); 
  369.  
  370.                 flag=1
  371.  
  372.                 if(pattern[0] == pattern[pattern_index]){ 
  373.  
  374.                     main_index--; 
  375.  
  376.                 } 
  377.  
  378.                 pattern_index = -1; 
  379.  
  380.             } 
  381.  
  382.             main_index++; 
  383.  
  384.             pattern_index++; 
  385.  
  386.         } 
  387.  
  388.         else{ 
  389.  
  390.             //printf("%c != %c\n",main[main_index],pattern[pattern_index]); 
  391.  
  392.             /*next value equals -1 means the condition we mismatch at the  
  393.  
  394.               first char,so we match again from the next char in main string 
  395.  
  396.             */ 
  397.  
  398.             if(next[pattern_index] == -1){ 
  399.  
  400.                 main_index++; 
  401.  
  402.                 pattern_index = 0
  403.  
  404.             } 
  405.  
  406.             else{ 
  407.  
  408.                 pattern_index = next[pattern_index]; 
  409.  
  410.             } 
  411.  
  412.         } 
  413.  
  414.     } 
  415.  
  416.     if(flag == -1){ 
  417.  
  418.         printf("Sorry,we find nothing."); 
  419.  
  420.     } 
  421.  

 

測試用例:

KMP.c

 

  1. #include "KMP.h" 
  2.  
  3.  
  4.  
  5. int main(){ 
  6.  
  7.     printf("=========origin===========\n"); 
  8.  
  9.     KMP_search_origin("abacababa","aba"); 
  10.  
  11.     printf("=========origin===========\n"); 
  12.  
  13.  
  14.  
  15.     printf("=========normal===========\n"); 
  16.  
  17.     KMP_search("abacababa","aba"); 
  18.  
  19.     printf("=========normal===========\n"); 
  20.  
  21.  
  22.  
  23.     printf("=========enhanced===========\n"); 
  24.  
  25.     KMP_search_enhanced("abacababa","aba"); 
  26.  
  27.     printf("=========enhanced===========\n"); 
  28.  
  29.     return 0; 
  30.  

next.c

 

  1. #include "KMP.h" 
  2.  
  3.  
  4.  
  5. int main(){ 
  6.  
  7.     int i = 0; 
  8.  
  9.     char * p = "abaabc"
  10.  
  11.     int length = strlen(p); 
  12.  
  13.     printf("=========origin===========\n"); 
  14.  
  15.     get_next_array_origin(p); 
  16.  
  17.     while(i < length){ 
  18.  
  19.         printf("next[%d] = %d\n",i,next[i]); 
  20.  
  21.         i++; 
  22.  
  23.     } 
  24.  
  25.     printf("=========origin===========\n"); 
  26.  
  27.  
  28.  
  29.     printf("=========normal===========\n"); 
  30.  
  31.     get_next_array(p); 
  32.  
  33.     i = 0; 
  34.  
  35.     while(i < length){ 
  36.  
  37.         printf("next[%d] = %d\n",i,next[i]); 
  38.  
  39.         i++; 
  40.  
  41.     } 
  42.  
  43.     printf("=========normal===========\n"); 
  44.  
  45.  
  46.  
  47.     printf("=========enhanced===========\n"); 
  48.  
  49.     get_next_array_enhanced(p); 
  50.  
  51.     i = 0; 
  52.  
  53.     while(i < length){ 
  54.  
  55.         printf("next[%d] = %d\n",i,next[i]); 
  56.  
  57.         i++; 
  58.  
  59.     } 
  60.  
  61.     printf("=========enhanced===========\n"); 
  62.  
  63.     return 0; 
  64.  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章