KMP算法(——模板習題與總結)


  KMP算法是一種改進的模式匹配算法,相比於朴素的模式匹配算法效率更高。下面講解KMP算法的基本思想與實現。

  先來看一下朴素模式匹配算法的基本思想與實現。

  朴素模式匹配算法的基本思想是匹配過程中如果該位置相等,繼續匹配各自的下一位,直至匹配完成,或者出現一位不匹配,如果該位置不相等,主串的匹配位置返回上次開始匹配位置的下一位,副串的匹配位置再次從頭開始。

實現程序如下:

  主串s,副串t,如果存在,返回t在s中第一次出現的位置,否則返回-1。

 1 int Index(char *s,char *t){
 2     int ls=strlen(s),lt=strlen(t);
 3     int i=0;//主串匹配位置 
 4     int j=0;//副串匹配位置 
 5     while(i < ls && j < lt){
 6         if(s[i] == t[j]){
 7             i++;
 8             j++;
 9         }
10         else{
11             i=i-j+1;
12             j=0;
13         }
14     }
15     if(j >= lt)//副串匹配完成
16         return i-j;
17     else
18         return -1; 
19 }

  可以設想,最糟糕的情況是每次的匹配的不成功均發生在了最后一位匹配,那么它的時間就主要浪費在了主串回溯的過程,比如00000001(7個0)和001,就有5次回溯是不必要的,它浪費的時間是隨着匹配成功之間的0的個數增多而增多,自然就想到沒有什么辦法去解決這個問題呢?

  下面就輪到KMP算法登場了。

  為了解決每次主串匹配不成功盡可能的往右邊滑的更遠一點問題,自然想到充分利用現在已知的信息,這里借鑒《算法競賽入門經典 訓練指南》中的圖解,具體體會一下KMP算法的精髓。

                   

  假如現在正在匹配主串中*位置和副串abbaaba的最后一個字符,發現二者不同(稱為失配),這時,朴素算法的做法是將串的開頭放在!!的位置上重新開始匹配,但KMP算法為了讓副串盡可能的回溯少一點,就利用現在已知的全部副串的信息來構建一個發現失配后副串盡可能的往右滑的更遠的狀態轉移圖,例如根據之前的匹配,我們知道了主串中*之前的副串長度的字符串,因為到*的時候才失配,我們可以知道副串右移一位、右移兩位甚至三位都是不行的,因為和自己匹配失敗,但是右移四位的時候就可能和后面的匹配了。這個右移的比較過程實際上就是一個串的前綴和后綴匹配的過程,為了滑的更遠,我們需要找到兩者最大的相似度,我們發現當走到副串的最后一個位置發現不匹配時前面的串的前后綴最大相似度是2,也就是ab的長度,故圖中可以看到當最后一個字符失配的時候下面的實體黑線指向了2,就完成了往右滑的盡可能遠的任務。

  那么怎么計算一個子串的前后綴的相似度呢?

  我們用next[j]數組來表示j下次應該返回的位置,函數定義如下:

  next[j]=   0,當j=0

      max({k|0<k<=j,且p0...pk=p(j-k)...pj},當此集合不為空

      1,其他

算法實現如下:

  副串t,用next數組存儲結果。

 1 void get_next(char t[],int next[])
 2 {
 3     int l=strlen(t);
 4     int i=0;//副串匹配位置 
 5     int j=-1;//next數組位置指針,初始化為-1,表示回溯邊界 
 6     next[0]=-1;//0表示長度為0的子串的前后綴相似度為-1,也表示回溯邊界
 7     while(i < l){
 8         if(j == -1 || t[i] == t[j]){
 9             i++;
10             j++;
11             next[i]=j;
12         }
13         else
14             j=next[j];
15     } 
16 }

下面將制作好的next數組應用到KMP算法中。

代碼如下:

 1 int Index_KMP(char *s,char *t){
 2     int ls=strlen(s),lt=strlen(t);
 3     int i=0;//主串起始匹配位置 
 4     int j=0;//副串起始匹配位置
 5     int next[maxn]={0};
 6     get_next(t,next);
 7     while(i < ls && j < lt){
 8         if(j == -1 || s[i] == t[j]){//增加j==-1表示回溯到了邊界的情況 
 9             i++;
10             j++;
11         }
12         else{ 
13             j=next[j];//j返回到合適的位置,而i不用改變 
14         }
15     }
16     if(j >= lt)//匹配完成
17         return i-j;
18     else
19         return -1; 
20 }

  可以看到KMP算法相較朴素的模式匹配算法,多了制作next數組,多了一個判斷條件,就成功的避免了主串不必要的回溯,節省了時間。下面給出幾道練習題目,鞏固知識。

  1.簡單的模板題HDU 1711 Number Sequence

  https://www.cnblogs.com/wenzhixin/p/7345115.html

  2.需要一點思維轉換HDU 2203 親和串

  https://www.cnblogs.com/wenzhixin/p/7344076.html

  3.加深印象HDU 3746 Cyclic Nacklace

  https://www.cnblogs.com/wenzhixin/p/7345115.html

  其他練習參考右側分類中的KMP。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM