KMP算法的理解


---恢復內容開始---

  在看數據結構的串的講解的時候,講到了KMP算法——一個經典的字符串匹配的算法,具體背景自行百度之,是一個很牛的圖靈獎得主和他的學生提出的。

  一開始看算法的時候很困惑,但是算法思想很簡單,就是在暴力匹配的基礎上得出的。

暴力匹配

  這里有必要說一下暴力匹配,暴力匹配更簡單,就是按照人的常規思維去匹配字符串,拿模式串(P)的第一個字符去和給定串(S)比較,S從左往右看,一看,第一個,呀~不對,啥也不說了,第一個都不對了,后邊還比個毛。所以,這一次比較,S中第一個字符開頭是匹配串就不可能了。然后就拿P的第一個字符,去和S中第二個字符開頭的比較,一看,對上了,有希望,再往后看,全對上了,恭喜你,程序結束了,沒對上,也不要氣餒,開始看S的第三個字符開頭的字符串……如此一遍遍重復,直到找到匹配串。

  上述算法暴力,簡單,但復雜度高,O(n2)的,一次就找到算你幸運,但是,形如“0000000001”的串S和形如“000001”的匹配起來就麻煩了,每次移動一個,看到P串的最后一個才發現不行,直到P串移動到最后。

KMP

  那么KMP就解決了這個問題,他讓P串不是簡單地往后移動一個了,而是移動k個,因為,我們每次比較時,前若干字符在上一次比較中的情況已經知道了,所以我們在下一次比較時,可以利用上一次比較的結果,不用再去做無用功了。

  那么關鍵來了,他為什么可以移動k個呀,你怎么保證你在移動k個的時候(略過了移動0~k個),沒有把正確答案錯過?思前想后,個人覺得,歸根結底還是根據模式串P的前綴和后綴重復性決定的。

  這里有兩個問題要解釋

  (1)前綴和后綴

  舉個栗子,字符串“ababa” 的所有前綴為“a”、“ab”、“aba”、“abab”,就是以第一個字符開頭,不包含最后一個字符的所有子串。同理,所有后綴就是“a”、“ba”、“aba”、“baba”,就是以最后一個字符結尾,不包含第一個字符的所有子串。

  (2)重復性

  關鍵來了,我覺得這是能使P向后移動k個而不是1個的基礎。

  

  以上圖所示的匹配為栗子,目前已經匹配了“ababa”(后邊的先不看),再往后匹配就失敗了,此時我要移動P串,KMP就不是移動一個了,而是移動k個,那么怎么決定k的大小呢,很顯然,已經匹配的串的后綴和前綴是有一定重復的,這樣利用重復的信息,在向后移動的時候,我們才能把前面一部分直接拿來用。

  就像手繪的這張圖一樣,整個矩形我們目前已經匹配上了,1部分和2部分是重復的,那么我們就可以向后移動k個,即1和2重合,毫無疑問,移動后,1部分是已經匹配好的,無需再檢測匹配了。

 

  所以,我們需要找,前綴和后綴最長的公共部分的長度。很顯然,公共部分越長,k就越小,說明當前越接近匹配串。為了后邊便於操作,我們在預處理中,遍歷得到模式串P的所有子串的最大公共部分長度,構成部分匹配表(next表),在后邊匹配的時候,直接查表就可以得到下一次前進的k值。

  例如在上述講前綴后綴時的栗子,前綴和后綴最長公共部分是“aba”,因此,表中對應的值就是3。

  當然,前進的k=已經匹配的字符串長度-該串對應的匹配表的值;(k>1時才有意義)

  

  行了,我理解的KMP算法已經講完了,最后舉個栗子驗證一下

  用別人的一張圖吧,應該沒事吧~~~

  這是上邊所說的部分匹配表,具體怎么得到的上邊已經講了。模式串P是“abababca”,待查找的串S為“bacbababaabcbab”。

  1. p的第一個和S的第一個不匹配,后移,此時那個公式沒用,直接向后移動一個。
  2. P[0]和S[1]匹配上了,但是P[1]和S[2]不匹配,a對應的值為0,公式沒用,直接向后移動一個。
  3. 然后跟c和b比較都不行,也是每次移動一個
  4. 再往后比較時,P中“ababa”和S中“ababa”匹配成功,再往后,P中是“b”,而S中是“a”,不匹配,此時查“ababa”對應的值是3,“ababa”長度是5,那么向后移動2個,繼續比較

  如圖,畫線部分,移動之后依然對其,就不用再比較了。

  剩下的工作類似,知道找到匹配串或者匹配失敗。

  如下是簡單的KMP實現代碼

 1 #include<stdio.h>
 2 #include<string.h>
 3 //定義next數組中的元素
 4 typedef struct Next
 5 {
 6     int value;
 7     char ch;
 8     int num;
 9 } Next;
10 Next next[100];
11 int kmp(char *s,char *p)
12 {
13     int len1 = strlen(s);    //S串
14     int len2 = strlen(p);    //模式串
15     int i=0,j=0;
16     int pos=i;  //記錄每次S中開始比較的位置
17     int succ=0;
18     while(len1-pos>=len2)
19     {
20 
21         i=pos;
22         while(s[i]==p[j]&&j<len2)
23         {
24             ++i;
25             ++j;
26         }
27         if(j==len2)
28         {
29             succ=1;
30             break;
31         }
32         if(next[j-1].value<1)
33         {
34             j=0;
35             ++pos;
36             continue;
37         }
38         pos=pos+j-next[j-1].value;   //S串中跳動的位置
39         j=0;    //每次模式串從頭比較,其實不用。。。
40     }
41     if(succ==1)
42         return i-len2;
43     else
44         return -1;
45 }
46 void getNext(char *p)
47 {
48     int len;
49     int k;
50     char pre[100],suf[100];
51     int flag=0;
52     for(int i=0; i<strlen(p); ++i)
53     {
54         len=i+1;
55         k=len-1;
56         flag=0;
57         next[i].ch=p[i];
58         next[i].num=i;
59         //從可能的最大的k開始尋找
60         while(k!=0)
61         {
62             for(int j=0; j<k; ++j)
63             {
64                 pre[j]=p[j];
65                 suf[j]=p[len-k+j];
66                 pre[j+1]='\0';
67                 suf[j+1]='\0';
68             }
69             if(strcmp(pre,suf)==0)
70             {
71                 flag=1;
72                 break;
73             }
74             else
75                 --k;
76         }
77         if(flag==1)
78             next[i].value=k;
79         else
80             next[i].value=0;
81     }
82 }
83 int main()
84 {
85     char s[100],p[100];
86     gets(s);
87     gets(p);
88     getNext(p);
89     int re = kmp(s,p);
90     if(re==-1)
91         printf("Fail\n",re);
92     else
93         printf("The substring is from %d to %d\n",re,re+strlen(p)-1);
94     return 0;
95 }

 

 

  


免責聲明!

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



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