一、簡述
KMP算法是對BF(Brute Force)算法做了很大改進的模式匹配算法,可以將時間複雜度從O(m*n)降低到O(m+n)。
二、KMP算法的思想
設主串s="acabaabaabcacaabc",模式t="abaabcac"
(一)分析BF算法
BF算法匹配過程如下:
第1趟
第2趟
第3趟
第4趟
第5趟
第6趟
分析BF算法的執行過程,發現造成BF算法慢的主要原因是回溯,在某一趟的匹配過程中,一旦匹配失敗,主串要回到本趟開始開始匹配字符的下一個字符,模式串要回到第一個字符,而這些回溯有時候是不必要的。對於第3趟來說,因爲s8 != t6,所以才需要進行第4趟的匹配,但是第4趟的匹配是不必要的,因爲在第3趟中,有s4 = t2,而t1 != t2,所以,s4 != t1。 同理第5趟匹配也是不必要的,因此可以從第3趟直接到第6趟。進一步分析第6趟,s6和t1的比較是多餘的,s7和t2的比較也是多餘的,因爲第3趟中有,s3~s7=t1~t5,則s6=t4,s7=t5,又因爲t1t2=t4t5,必有s6s7=t1t2,因此第6趟的比較可以從s8和t3開始,也就是說,在第3趟的比較中,s8和t6匹配失敗後,i指針不動,將模式串向右“滑動”,用t3對準s8繼續匹配,依次類推。用這樣的處理方法,指針i不需要進行回溯。
綜上所述,當si和tj匹配失敗後,我們希望指針i不回溯,而是讓模式串向右滑動至某個位置k,使得si和tk繼續比較。要滿足這個假設,則需要滿足:
t1t2...tk-1 = si-k+1si-k+2...si-1
等號左邊是模式串的前k-1個字符,等號右邊是主串si之前的k-1個字符。
又因爲k<j,則:
t1t2...tk-1 = tj-k+1tj-k+2...tj-1 (1.1)
結論:
在si和tj匹配失敗後,如果在模式串中存在滿足式1.1的字串,即模式串的前k-1個字符和模式串tj前的k-1個字符相等時,模式串就可以滑動到tk和si對準繼續比較。
(二)next函數
模式串的每一個tj都對應一個k值,由式1.1可知,這個k值只依賴與模式串本身,與主串無關。用函數next(j)來表示tj對應的k值。由以上分析可知,next(j)函數的性質如下:
1)next(j)是一個整數,且0<=next(j)<j;
2) 爲了使得t的右移不丟失任何匹配成功的可能,當有多個滿足式1.1的k值時,應取最大的,這樣向右滑動的距離最短,向右滑動的字符爲j-next(j)個;
3)當不存在滿足式1.1的字串時,則k=1;即模式串t1和si匹配,此時模式串滑動最遠,爲j-1個字符。
next函數定義如下:
(三)KMP算法
假設模式串t字符對應的每一個k值都計算好,並且存放在next數組中。指針i和指針j分別指向主串和模式串,依次比較,當si=tj時,指針i和指針j加1,繼續匹配;當si != tj時,指針i不變,j=next[j],繼續匹配;當j=0時,指針i和指針j加1,即模式串退回至第一個字符,主串移動到下一個字符繼續匹配。
三、KMP算法代碼實現
package DataStructureandAlgorithm;
/**
* @author
* @date 2020/1/11 21:07
*/
public class KMP {
/**
* @description next[j]值的含義:
* 1、表示主串與模式串匹配失敗後,
* 模式串應該以索引next[j]字符的值與主串匹配失敗的索引的值繼續比較
* 2、表示索引j字符前的最大前綴後綴的後一個字符所在的索引
* */
public int[] getNext(String t){
int k=-1,j=0;
int len = t.length();
int[] next = new int[len];
//模式串的第一個字符next值爲-1
next[0] = -1;
while(j<len-1){
if(k == -1 || t.charAt(k) == t.charAt(j)){
k++;
j++;
next[j] = k;
}else{
k = next[k];
}
}
return next;
}
//返回模式串在主串的索引
public int getIndex(String s,String t,int next[]){
int i = 0,j = 0;
int sLen = s.length();
int tLen = t.length();
while(i<sLen && j<tLen){
if(next[j] == -1 || s.charAt(i) == t.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
}
if(j == tLen){
return i-tLen;
}else{
return -1;
}
}
public static void main(String[] args){
KMP kmp = new KMP();
//s = acabaabaabcacaabc
//t = abaabcac
String s = "aabcbabcaabcaabab";
String t = "abcaababc";
int next[] = kmp.getNext(t);
System.out.println(kmp.getIndex(s,t,next));
}
}