字串匹配——KMP演算法的Java實現
阿新 • • 發佈:2019-02-07
開始複習演算法,複習到字串這一結構時,一個經典的問題就是兩個字串的匹配問題。
比如:在主串ssdfgasdbababa
中找是否存在一個asdba
的子串。
傳統方法——暴力匹配
用傳統的方法就是暴力匹配,從主串中一個個地和子串匹配。
最壞的情況下,就是匹配到最後一步才得到結果。
其時間複雜度為O((m-n)*n)
,其中主串的長度為m,子串的長度為n。
KMP演算法
以下為自己的理解,表達上難免有些口語化:)
KMP演算法的核心思想就是:如果A==B,B!=C,那麼A必然不等於C!,這樣一來就省掉了比較A和C的步驟。
KMP的大致思想就是:利用一個next陣列,儲存子串中每個字元前面的字首和字尾的最大相同匹配長度,為了在與主串匹配時,如果存在相同的部分,就可以通過next陣列來“跳過”已經相同的部分,省掉一些匹配操作。
如果想看詳細解釋,推薦另一篇部落格:KMP演算法最淺顯理解——一看就明白,不過提醒下這篇是基於C++實現的,不過原理講的挺好的。
程式碼實現與理解
在KMP演算法上分兩部分:一部分是計算next陣列,另一部分就是子串和主串匹配。其實兩者實現原理是一致的。
計算next陣列
首先計運算元串的next陣列
在這裡要解釋下,不同版本的KMP演算法雖然原理一樣,但是各自對next的值有不同的理解,在這裡說明一下,next陣列中:
- 為0:代表字首和字尾沒有匹配的,同時預設子串第一個元素的next值為0,比如
abc
每個字元的next值都為0; - 為1:代表字首和字尾有一個字元匹配的,比如
aba
a
的next值為1; - 為2:代表字首和字尾有兩個字元匹配的,比如
abab
中,最後一個字元b
的next值為2; - 以此類推3,4,5······
public static int[] calNext(String str){
int length = str.length(); //子串的長度
int j = 0; //j是next陣列的下標
int[] next = new int[length]; //初始化子串
next[0] = 0; //子串的第一個元素肯定沒有匹配的,預設為0
for(int q = 1; q<length; q++){
while(j>0 && str.charAt(j)!=str.charAt(q)){ //如果不相等
//j下標表示next陣列的index,q下標表示子串的index
//j>0說明前面仍有部分匹配的情況
//注意:在next陣列中next[j]永遠小於或等於j的,保證當不相等的情況下,夠往前回溯
j = next[j-1]; //回溯
}
if (str.charAt(q)==str.charAt(j)) { //如果相同,則j加1
j++;
}
next[q] = j;
}
return next;
}
利用next陣列,匹配主串和子串
KMP主方法思想和上面求next陣列思想是一致的,只不過一個是串內匹配,這個是兩個串之間匹配。
直接上程式碼:
//str為主串,dest為子串,next為上面得到的next陣列
public static int kmp(String str, String dest, int[] next){
for(int i=0,j=0;i<str.length();i++){
while(j>0&&str.charAt(i)!=dest.charAt(j)){ //說明子串和主串當前下標的元素不匹配
//j>0說明前面有相同的部分
j = next[j-1]; //回溯
}
if(str.charAt(i)==dest.charAt(j)){
j++;
}
if(j == dest.length()){ //說明匹配到了子串末端,找到匹配的地方
return i-j+1;
}
}
return -1; //如果找不到匹配,返回-1
}
主程式
最後就是在主程式中呼叫了。
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = "ssdfgasdbababa";
String dest = "ababa";
int[] next = calNext(dest);
System.out.println(kmp(str, dest, next));
}
總結
當時理解KMP演算法還是有點繞,稍微有些難度(原諒我演算法基礎不咋樣~)
最後說一下,KMP演算法在字串存在部分匹配才會體現它的效率,否則和一般方法區別不大。