天天看點

java kmp算法_KMP算法詳解及其Java實作

KMP算法,又稱作“看貓片”算法(誤),是一種改進的字元串模式比對算法,可以在O(n+m)的時間複雜度以内完成字元串的比對操作,其核心思想在于:當一趟比對過程中出現字元不比對時,不需要回溯主串的指針,而是利用已經得到的“部分比對”,将模式串盡可能多地向右“滑動”一段距離,然後繼續比較。

![KMP(看貓片)算法](https://images2018.cnblogs.com/blog/1485189/201809/1485189-20180909160802638-149491311.jpg)KMP(看貓片)算法

![]()

1. 樸素的字元串模式比對算法

求一個字元串(模式串)在另一個字元串(主串)中的位置,稱為字元串模式比對。

在樸素的字元串模式比對算法中,我們對主串S和模式串T分别設定指針i和j,假設字元串下标從0開始,初始時i和j分别指向每個串的第0個位置。在第n趟比對開始時,i指向主串S中的第n-1個位置,j指向模式串T的第0個位置,然後逐個向後比較。若T中的每一個字元都與S中的字元相等,則稱比對成功,否則,當遇到某個字元不相等時,i重新指向S的第n個位置,j重新指向T的第0個位置,繼續進行第n+1趟比對。

例如,我們對模式串T=“abaabcac”和主串S=“abcabaabaabcacb”進行比對。如圖1.1,此時正在進行第4趟比對,S[3...7]與T[0...4]均相等,但當i=8,j=5時,S[8]與T[5]不相等,比對失敗。于是,置i=4,j=0,相當于将模式串向右移動一位後,重新開始下一趟比對,如圖1.2。

![圖1.1](https://images2018.cnblogs.com/blog/1485189/201809/1485189-20180909160938585-1146094368.png)

圖1.1 當i=8,j=5時,字元不相等,比對失敗

![圖1.2](https://images2018.cnblogs.com/blog/1485189/201809/1485189-20180909161818355-942371414.png)

圖1.2 将模式串向右移動一位後,重新開始下一趟比對

利用此種方法進行字元串比對,最壞情況下時間複雜度為O(n*m),其中n和m分别為主串和模式串的長度。

2. 改進的字元串模式比對算法——KMP算法

在上面的例子中,我們可以看到,當i=8,j=5時,S[8]與T[5]不相等,于是置i=4,j=0,相當于将模式串向右移動一位,再開始下一趟比對。然而,通過觀察我們可以發現,之後的兩趟比對,即i=4,j=0以及i=5,j=0都是不必要的。這是因為,在之前的一趟比對過程中,我們已經部分比對了T的子串“abaab”。此時将T向右移動一位,則相當于對T中的“abaab……”與S中的“baab……”進行比對,顯然無法比對成功。繼續右移T,則相當于對T中的“abaab……”與S中的“aab……”進行比對,依然無法比對成功。隻有當T向右移動3位後,此時對T中的“abaab……”與S中的“ab……”進行比對,才會有成功的可能,也就有必要向後繼續進行比較。如圖2.1。

![圖2.1](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107202953821-445138636.png)

圖2.1 比對失敗時,将T向右移動3位後,才有繼續比較的必要

是以,當i=8,j=5,T的子串“abaab”已經比對成功,而其後一位字元卻不相等時,不必回溯i指針,置i=8,j=2,繼續向後比較,相當于将T向右移動3位,并從T的第3位開始向後比較。如圖2.2。

![圖2.2](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203048317-1520055718.png)

圖2.2 比對失敗後,直接置i=8,j=2,繼續向後比較

這就是KMP算法的基本思路。對于模式串T中的前j個字元組成的子串,設定數組next[j]存放一個值,當模式串T比對至第j個字元時與主串不相等,則i指針不變,将j指針置為next[j]的值,然後繼續進行比較。在上例中,串“abaab”為模式串T的前5個字元組成的子串,令next[5]=2,當i=8,j=5時,S[8]與T[5]不相等,于是置i=8,j=next[j]=next[5]=2,然後繼續進行比較。

是以,KMP算法的核心在于求出數組next,即模式串T中每一個長度為j (0

next數組求解算法

在求解next數組前,我們首先需要了解next數組的含義。回到前面的例子,當T的子串“abaab”的下一個字元與主串不相等時,主串的指針i不變,j回溯至2,指向T的第3個字元,其本質是因為串“abaab”的字首和字尾有一個長度為2的最長公共串“ab”,是以我們省略了字首“ab”和字尾“ab”的比較過程,直接對它們的後一個字元,即T[2]和S[8]進行比較。

再看另一個例子,假設有模式串T=“abacaabadad”,其已部分比對完T[0...7],即“abacaaba”,在比對T[8]時遇到比對失敗,因T[0...7]的字首和字尾有長度為3的最長公共串“aba”,是以next[8]=3,置j=next[j]=next[8]=3,i不變,然後從T[3],即T的第4個字元開始比較。如圖2.3。

![圖2.3](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203117367-612235735.png)

圖2.3 比對T[8]時失敗,i不變,j回溯至3

總之,對于模式串T,next[j]代表了T的前j個字元組成的子串中,其字首和字尾的最長公共串的長度。

求解字元串T的next數組的算法如下:

next[0]=-1, next[1]=0。

在求解next[j]時,令k=next[j-1],

比較T[j-1]與T[k]的值,

a. 若T[j-1]等于T[k],則next[j]=k+1。

b. 若T[j-1]不等于T[k],令k=next[k],若k等于-1,則next[j]=0,否則跳至3。

下面以模式串T=“abaabcac”為例,給出求next數組的過程:

next[0]=-1, next[1]=0。

當j=2時,k=next[j-1]=next[1]=0,由于T[j-1]=T[1]=‘b’,T[k]=T[0]=‘a’,T[j-1]不等于T[k],令k=next[k]=next[0]=-1,是以next[2]=0。

當j=3時,k=next[j-1]=next[2]=0,由于T[j-1]=T[2]=‘a’,T[k]=T[0]=‘a’,T[j-1]等于T[k],是以next[3]=k+1=1。

當j=4時,k=next[j-1]=next[3]=1,由于T[j-1]=T[3]=‘a’,T[k]=T[1]=‘b’,T[j-1]不等于T[k],令k=next[k]=next[1]=0。此時T[k]=T[0]=‘a’,T[j-1]等于T[k],是以next[4]=k+1=1。

當j=5時,k=next[j-1]=next[4]=1,由于T[j-1]=T[4]=‘b’,T[k]=T[1]=‘b’,T[j-1]等于T[k],是以next[5]=k+1=2。

當j=6時,k=next[j-1]=next[5]=2,由于T[j-1]=T[5]=‘c’,T[k]=T[2]=‘a’,T[j-1]不等于T[k],令k=next[k]=next[2]=0。此時T[k]=T[0]=‘a’,T[j-1]不等于T[k],再令k=next[k]=next[0]=-1,是以next[6]=0。

當j=7時,k=next[j-1]=next[6]=0,由于T[j-1]=T[6]=‘a’,T[k]=T[0]=‘a’,T[j-1]等于T[k],是以next[7]=k+1=1。

将next數組全部求出之後,隻需在簡單的比對算法上稍作修改,便得到了KMP的比對算法:當模式串T比對至第j個字元時比對失敗,i指針不變,将j指針置為next[j]的值,若j的值為-1,則将i和j同時加1。随後繼續進行逐個的比較。

下面以模式串T=“abaabcac”和主串S=“abcabaabaabcacb”進行比對為例,給出KMP比對算法的全過程。

之前已經求得模式串T的next數組為[-1, 0, 0, 1, 1, 2, 0, 1]。

初始時,i=0,j=0,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203144421-1790051929.png)

2. i=1,j=1,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203301705-1573255069.png)

3. i=2,j=2,比對失敗。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203312482-506404756.png)

4. i=2,j=next[2]=0,比對失敗。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203321486-1584379594.png)

5. i=2,j=next[0]=-1,比對失敗。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203329813-1181552146.png)

6. i=2+1=3,j=-1+1=0,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203345507-1570749968.png)

7. i=4,j=1,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203356011-1340913809.png)

8. i=5,j=2,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203406410-1127902417.png)

9. i=6,j=3,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203417066-1414119806.png)

10. i=7,j=4,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203426091-1146892290.png)

11. i=8,j=5,比對失敗。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203437862-1315035527.png)

12. i=8,j=next[5]=2,比對成功。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203448412-1574307342.png)

13. 繼續向後比較,中間過程均比對成功,故不再贅述,當i=13,j=7時,模式串比對完成。

![](https://img2018.cnblogs.com/blog/1485189/201811/1485189-20181107203458300-1121595444.png)

以上就是KMP比對算法的全過程。總結一下,KMP算法的實質就是以空間換時間,在比對之前将模式串的一些資訊存儲起來(next數組),在随後的比對過程中利用這些資訊減少不必要的比對次數,以提高比對效率。在實際的應用過程中,簡單模式比對算法的執行時間常常接近于KMP算法,僅當主串與模式串有很多“部分比對”時,KMP算法才能顯著提升性能。

3. KMP算法的Java實作

下面給出KMP算法的Java代碼。整個算法分為兩部分,一是next數組的求解,二是KMP比對過程。

public class KMP {

public static int[] getNextArray(char[] t) {

int[] next = new int[t.length];

next[0] = -1;

next[1] = 0;

int k;

for (int j = 2; j < t.length; j++) {

k=next[j-1];

while (k!=-1) {

if (t[j - 1] == t[k]) {

next[j] = k + 1;

break;

}

else {

k = next[k];

}

next[j] = 0; //當k==-1而跳出循環時,next[j] = 0,否則next[j]會在break之前被指派

}

}

return next;

}

public static int kmpMatch(String s, String t){

char[] s_arr = s.toCharArray();

char[] t_arr = t.toCharArray();

int[] next = getNextArray(t_arr);

int i = 0, j = 0;

while (i

if(j == -1 || s_arr[i]==t_arr[j]){

i++;

j++;

}

else

j = next[j];

}

if(j == t_arr.length)

return i-j;

else

return -1;

}

public static void main(String[] args) {

System.out.println(kmpMatch("abcabaabaabcacb", "abaabcac"));

}

}

參考資料及緻謝

在學習KMP算法思路的過程中,我大量參考了“王道考研系列”的《資料結構聯考複習指導》一書,以及CSDN部落客v_JULY_v的文章:從頭到尾徹底了解KMP,特此鳴謝。

同時感謝微網誌部落客@回憶專用小馬甲和實驗室的大紅袍CocoXu提供的大量貓片,讓我在學習KMP算法的過程中擁有持續的動力。

文章中如有錯誤,歡迎指正!