KMP演算法next陣列
暴力法求解模式匹配
example:
S:A B C A B C A B C ...
T:A B C A B X
如果使用暴力法S匹配T,當主串指標為6(均以1開始計數)、模式串指標為6時不匹配,此時主串指標會回溯到2,T指標回溯到初始位置0,重新開始比較,以此類推。
思想
example:
S:a b c d e f g h i j k l m n
T:a b c d e a
KMP演算法使得主串指標i不進行回溯,模式串指標根據前後綴重複的情況進行重新定位,從而提高效率
有模式串abcdex當匹配到x時匹配失敗,已知a與bcde均不同,那麼主串指標就不需要再返回bcde與模式串進行比較了,如此就減少了主串指標的回溯問題
最大重複前後綴
最大重複前後綴是串的字首和字尾重複的字串
example
abcabc 最大重複前後綴為abc
abaca 最大重複前後綴為a
aaaa 最大重複前後綴為aaa(不是aaaa)
有重複時
上邊記錄的是不會出現重複的情況,當出現重複情況時是如何?
example
S:a b c a b c
T:a b c a b x
當匹配到位置x處失敗
先考慮i回溯問題:已知a與b、c不相等,那麼i指標必然不需要回溯到b、c處與T串作比較,接著考慮第四位a與第五位的b,發現與第一位的a、b一致,在主串比較到第四五位時已經比較過a、b了,那麼a、b的比較也是不需要的,固i仍然不需要回溯,j指標從第三位開始與主串比較即可(最大重複前後綴處的下一位)
S:a b c a b c
T: a b c a b
總結KMP演算法的優勢:
- 主串指標不需要進行回溯(只需要模式串移位即可)
- 不匹配時模式串也不一定從初始位置開始(找最大字首字尾長度處開始就行,字首和字尾一致)
next陣列
根據分析,KMP演算法與主串沒有關係,取決於模式串的每個位置的前後綴長度,這個記錄每個位置前後綴長度的陣列就稱為next陣列。當匹配失敗時使用next陣列進行模式串的位置與主串當前位置的重新匹配,準確的說應該是最大前後綴長度+1,從下一位開始比較。
現在整個KMP演算法的關鍵轉化為next陣列的求解
import java.util.Arrays;
// kmp 模式匹配演算法
public class KMP{
private String p;
private int[] next;// 不匹配得下個位置
// param
// s:查詢字串
public KMP(String p){
this.p = p;
this.next = new int[p.length()+1];
this.genNext();
}
// 生成next陣列
private void genNext(){
String p = "0"+this.p;// 佔位
int head = 0;// 字首
int tail = 1;// 字尾
this.next[0] = -1;// 佔位
this.next[1] = 0;
while (tail<this.p.length()){
if (head==0||p.charAt(head)==p.charAt(tail)){
head++;
tail++;
this.next[tail] = head;
}else{
// 如果兩個值不相等時則判斷該值得最大字首處是否相等
// 如果相等,最大字首+1
// 直接比較最大前後綴的原因在於,如果不是最大前後綴
// 那麼前後一定不同,比較無意義,固直接比較當前位置
// 的最大前後綴處即可
// 由於左邊前幾個與後邊幾個是對稱得,如果左邊所有都
// 相等,那麼右邊也是相等的。
head = this.next[head];
}
}
System.out.println(Arrays.toString(this.next));
}
private int getNext(int index){
return this.next[index];
}
// KMP演算法查詢
public int KMPSearch(String s){
int sLen = s.length();
int pLen = this.p.length();
int i = 0;// 查詢字串指標
int j = 1;// 模式串指標
String p = "0"+this.p;
while (i<sLen&&j<=p.length()){
// 如果兩者相同則雙指標後移
if (j==0||s.charAt(i)==p.charAt(j)){
i++;
j++;
}else{
j=this.getNext(j);// 不從頭開始
}
}
System.out.println("------->"+i);
// 返回開始位置,所以需要當前位置減去模式串得長度
if (j>pLen) return i-pLen;
else return -1;
}
public static void main(String[] args){
String s1 = "aaaaab";
String s2 = "aaaaaa";
String p = "aaaab";
KMP kmp = new KMP(p);
System.out.println("kmp.KMPSearch(s1) == 1=====>"+(kmp.KMPSearch(s1) == 1));
System.out.println("kmp.KMPSearch(s2) == -1=====>"+(kmp.KMPSearch(s2) == -1));
}
}
next陣列與模式串均以索引1開始(方便),所以我在模式串的開始填充了0作為標記。比較難以理解的地方在於不匹配時head=next[head]
這一步。
不相同時尋找head處的最大前後綴是多少,繼續比較該處的值與當前tail指標處值即可,原理與KMP的原理相同,不過是自身與自身進行比較,當head和tail不同時tail指標不進行回溯,head進行回溯(弄懂了主串與模式串的KMP原理就弄懂了這裡)。
引用’v_july_v’文章中的圖
當P0到Pk-1與Pj-k到Pj-1相同,當Pj≠Pk時k回溯到next[k]繼續與指標j處值比較,而指標j不進行回溯