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.  

 

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