KMP算法是非常高知名度字符串匹配算法,也非常的牛P,具體在哪呢?這個算法每次我想起來的時候,我就要看一遍,自信的覺得OK,完全掌握沒問題,然后過不了幾天忘得連個渣都不剩,你懂的.
先說字符串匹配算法,這個我最開始接觸java的時候,這個需求可以算是入門級別的.
雙重迭代,逐個匹配,直到找到完全匹配,或者主串沒有足夠長度.
public static int indexOf(String m, String p) {
int index = -1;
for (int i = 0; i < m.length() && m.length() - i >= p.length(); i++) {
index = i;
for (int j = 0; j < p.length(); j++) {
if (p.charAt(j) != m.charAt(i + j)) {
index = -1;
break;
}
}
if (index != -1) {
return index;
}
}
return index;
}
這種暴力迭代的算法,因為雙重的for循環,導致時間復雜度為O(m*n),其實就是O(n2),
這個算法其實也是JDK
中String的indexof(String)的默認算法.考慮到java中String并不是為大文本設計的,編程的String使用場景必然不會有太長的String文本,使用這種算法消耗的時間并不明顯.考慮到KMP算法需要額外的開辟空間存放Next數組.所以采用了這種簡單地做法.
雙重for循環另一方面說,其實就是雙指針,i
和j
的回溯.每完成一次模式串的對比,i指針就需要回到i+1的位置,j指針則回到初始位置.KMP算法的核心就是如果在完成原串和模式串的匹配后,能夠不回溯i
指針,這樣i指針就只走一次,那么匹配的復雜度就是O(m).大神們通過利用已知模式串自身的有效信息,完成i
指針不回溯,通過移動j
指針,讓模式串最大化的有效移動.
那么下面要做的事情比較簡單,首先是如和做到i指針的不回溯,其實就是下面的推導.
T是原串,P是模式串.index是i指針這一次對比的初始位置
假如匹配的過程中
T[i] != P[j]
已知
T[index,i-1] = P[0,j-1]
假如在0
和j
之間有一個最大的k
值使得P[0,k-1] = P[j-k,j-1]
那么必然P[0,k-1] = T[i-k, i-1]
這樣下次匹配,就可以從T的i位置,P的k位置進行新的比較.
以這個公式作為出發點,下面的重點就是k的計算.
即在0和j之間尋找最大的k值,P[0,k-1] = P[j-k,j-1]
這個等式的重點是則需要根據不同的j值來確定不同的k值.
k的推導過程是下邊的過程
假設有P[0,k-1] = P[j-k,j-1] (Next[j] = k)
如果P[k] = P[j]
那么可以確定有P[0,k] = P[j-k,j]
,即Next[j + 1] = k + 1
如果P[k] != P[j]
那么必然可以確定不存在一個大于k的k2值,使得前面的公式P[0,k-1] = P[j-k,j-1]成立.
只能從小于k的子集中查找.
又可以知道k' = Next[k]
的值,
也符合P[0,k' -1] = P[k-k' ,k-1]
,
如果此時存在P[k']==P[j]
,
那么Next[j+1] = k'+1
這里還需要注意的是求k'這其實是一個遞歸的過程,因為需要一直遞歸k前邊的子串使得確定不存在k'的值(這里真的相當難理解)
這樣通過一次循環既可以推得所有的Next數組.
所以Next數組的求解方案:
private static int[] getNext(String p) {
int[] next = new int[p.length()];
next[0] = -1;
int k = -1;
int j = 0;
while (j < p.length() - 1) {
if (k == -1 || p.charAt(k) == p.charAt(j)) {
next[++j] = ++k;// 如果k位置的值和當前位置的值相等,那么當前位置的next[cur] = k + 1
} else {
k = next[k];// 找到k'
}
}
next[0] = 0;
return next;
}
在有了next數組的基礎上,按照上面所述,只需要單次移動i指針就可以完成模式串的匹配查找.因為需要優先計算Next數組,所以,這個算法的復雜度就是O(m+n),不過也多了O(n)的空間.
public static int kmpIndexOf(String m, String p) {
int index = -1;//記錄第一個匹配的位置
final int[] next = getNext(p);
int i = 0;//記錄原串的i指針,也是下一次匹配時,原串的初始位置
int k = next[i];//下一次匹配時,模式串的初始位置
//跳出循環的條件,原串的剩余部分不夠模式串長度
while (i < m.length() && m.length() - i >= p.length()) {
index = i;//記錄當前的位置
for (int j = k; j < p.length(); j++) {
i++;//原串單次循環,只要經過一次對比,i指針往前一步
k = next[j];//更新k值,為下一次P串的初始位置.
if (p.charAt(j) != m.charAt(index + j)) {//發現不等,跳出開始從i指針的記錄位置,重新匹配.
index = -1;
break;
}
}
if (index != -1) {//匹配完成,全部相等,跳出.返回index
return index;
}
}
return index;
}
其實寫了這些我也不太會貼圖,深感詞不達意,也不能說自己完全掌握的明明白白.不過,我也試過,無論我看過多少關于KMP算法的文章,也都感覺不能完全理解.直到自己手動寫的時候,去自己思考的時候,就很容易理解了.說白了,看的迷糊沒事兒,寫一遍就OK.
這也是要寫這篇文章的目的,讓自己在思考一遍.坦白說,就是自己寫給自己看的,也希望過兩天我自己還能看得懂.