KMP算法簡單應用


K(看)M(毛)P(片)算法最常用在字符串匹配。給定一個長的字符串(target string)和一個短的字符串(pattern string),要求判斷pattern string是否是target string的子串,如果是,則返回子串的首個字符的下標;如果否,則返回-1。

解決這個問題最常想到的辦法就是brutal force,即從target string第一個字符開始與pattern string比較,如果相等則比較target string和pattern string的下一個字符,如果不等則返回到target string中相等的字符的下一個字符。換句話說,假設我們用target和pattern分別表示兩個字符串的指針,那么每一次比較不管兩個string匹配到何種程度,只要不是完全匹配(即匹配完成),那么target永遠只能增加1,這個算法的復雜度為O(mn).(m=strlen(target string),n=strlen(pattern string)).

以下是這個算法的C代碼。

 1 int strstr(char *target, char *pattern)
 2 {
 3   int i,j;
 4   for(i=0;;i++)
 5     {
 6         for(j=0;;j++)
 7         {
 8             if(pattern[j]==0)return i;
 9             if(target[i+j]==0)return -1;
10             if(target[i+j]!=pattern[j])break;
11         }
12     }
13 }

導致這個算法時間復雜的關鍵,在於我們每次只能將target指針加一,而不能充分利用之前已匹配部分的信息。一個很好的例子由http://kenby.iteye.com/blog/1025599 給出,事實上我們可以通過利用已匹配部分的信息,讓每次比較失敗后target跳過多個位置。

下面給出KMP的代碼再給出一些解釋。

 1 int* overlay(char *str)
 2 {
 3     int *a,len=strlen(str),i,index;
 4     a[0]=-1;//美好的約定
 5     a=(int *)calloc(len,sizeof(int));
 6     for(i=1;i<len;i++)
 7     {
 8         index=a[i-1];//上一個
 9         while(index>=0&&a[index+1]!=a[i])
10         {
11             index=a[index];
12         }
13         if(a[index+1]==a[i])
14         {
15             a[i]=index+1;
16         }
17         else a[i]=-1;
18     }
19     return a;
20 }
21 
22 int strstr(char *target,char *pattern)
23 {
24     int i,j,*a,len1=strlen(target),len2=strlen(pattern);
25     a=overlay(pattern);
26     for(i=0,j=0;i<len1&&j<len2;)
27     {
28         if(target[i]==pattern[j])
29         {
30             i++;
31             j++;
32         }
33         else if(j==0)i++;//若第一個字符就不相等,則對target的指針加1即可
34         else j=a[j-1]+1;
35     }
36     if(j==len2)return i-j;
37     else return -1;
38 }

overlay函數是用遞推求出pattern串每一個位置對應的覆蓋值,算法在http://blog.csdn.net/power721/article/details/6132380有解釋,不贅述。覆蓋值數組的含義在上述博客中沒有解釋得很清楚,我舉個例子。

首先假設這個字符串名為str。首字符的覆蓋值是-1,這是約定。從第二個字符開始,我們看到b的覆蓋值是-1,什么意思呢,意思就是在b之前的子串(包括b),不存在k滿足str[0]str[1]...str[k-1]str[k]=str[1-k]str[2-k]...str[1]。接下來,第三個字符a的覆蓋值為0,表示在a之前的子串(包括a),存在k=0使得str[0]str[1]...str[k-1]str[k]=str[2-k]str[3-k]...str[2]。第五個字符b的覆蓋值為1,表示在b之前的子串(包括b),存在k=1使得str[0]str[1]...str[k-1]str[k]=str[4-k]str[5-k]...str[4]。

至於overlay函數的遞推求法,博客里有說明遞推過程,我也是看了好一會才看清楚orz...下面配個圖,圖里中括號里面是相同的字符串,小括號里面也是相等的字符串,以此第三次,第四次一直往下找...大家才思敏捷,一定可以看得懂。

 

其余的東西在以上兩篇博客都講得很清楚,我也不再贅述。算法導論里有關於KMP算法攤還分析的一些證明,在不同的地方,上文所述的覆蓋值的含義有可能有一點點差別,命名也有不同,但總的意思和算法都是差不多的。

部分代碼參考了https://leetcode.com/problems/implement-strstr/?tab=Description

 


免責聲明!

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



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