【演算法】KMP經典演算法,你真的懂了嗎?
有關KMP演算法的書籍、帖子、部落格鋪天蓋地,但是你真的能看懂?你知道為什麼要有next陣列,next陣列到底什麼意思,又該怎麼求next陣列,有了next陣列之後又該怎樣判斷模式串和主串是否匹配成功?本文絕對不是講解KMP演算法最細緻的一篇文章,但卻是為了解決大家的疑惑而寫的一篇文章。
KMP的概念
首先說說什麼是KMP演算法,說白了,就是不希望用簡單的兩層迴圈遍歷兩個串那樣去看能否匹配成功。簡單樸素的字串匹配是,一旦匹配不成功,主串要回到匹配開始的起始位置,然後加1再和模式串從頭匹配。
如此效率太低,有沒有效率更高的呢,顯然是有的,這就是KMP,KMP演算法效率之所以高,是因為主串不用回溯,只要模式串回溯就可以,也就是上面的j=4,s=4發現不匹配之後,j=4不變,s改變,那s到底怎麼改變呢,這就是next陣列的作用了!
next陣列的概念
看過KMP的人都知道字串字首和字尾這兩個概念,而我並不會說這兩個概念。next陣列通俗易懂的理解就是,對於模式串中每一個字元,看它前面字串中存在的字首和字尾匹配的最長長度。什麼意思呢?
仔細看,應該可以很快明白過來,所謂的next陣列就是求前面串中前後綴相等的最大長度而已。
next陣列的作用
儘管知道了next陣列,但是它做什麼作用呢?知道求這個陣列,但是仍然不能知道它有什麼意義,那也白搭不是?接下來,便是KMP中的精髓,即next陣列有什麼作用。請看下圖:
從上面過程可以知道,next陣列就是儲存著當主串和模式串不匹配時,接下來與主串j比較的模式串中s的位置,即s=next[s]。
next陣列的求法
前面有對next求解的過程,然而那只是為了理解next的含義,真正演算法程式設計卻無法那樣直觀求出,那該如何求解next陣列呢?這裡用到了類似並查集的演算法。
主串:abababbbab
- 首先next[0]=-1,next[1]=0;
- 之後每一位j的next求解:
比較j-1字元與next[j-1]是否相等,
如果相等則next[j]=next[j-1]+1,
如果不相等,則比較j-1字元與next[next[j-1]]是否相等,
1) 如果相等則next[next[j-1]]+1,
2)如果不相等則繼續以此下去,直到next[…]=-1,則next[j]=0.
通俗易懂的話來說就是你要求解當前位的next值,則看前一位與前一位next所在位字元是否相等,相等則當前位的next等於前一位next+1,如果不等就找前一位next的next與前一位比較,如果相等,當前位的next等於那個與前一位字元相等的字元所在位置+1,如果還是不相等,則以此類推,直到出現比較位為next[]=-1,那當前位的next就等於-1。
然而在演算法求解的時候,我們應該這樣去理解,求解下一位的next等於當前位與當前位的next比較。演算法如下:
//next的求解
private static void getNext(int[] next, String str) {
next[0]=-1;//初始化
int k=-1;//記錄當前位的next
int j=0;//當前位下標
while(j<str.length()-1){//求解完所有字元的next
if(k==-1||str.charAt(j)==str.charAt(k)){//比較當前位與當前位next字元是否相等
j++;
k++;//當前位的next值+1作為下一位的next值
next[j]=k;//求解出下一位的next值
}else{
k=next[k];//如果不相等則找當前位的next的next與當前位比較
}
}
}
完整的KMP演算法
在搞懂上面所有東西之後,那字串匹配演算法KMP就呼之欲出了。簡言之,將主串與模式串匹配,相等的話彼此都+1,如果不等,主串j不回溯,將模式串的s=next[s]與主串j比較,相等彼此+1,以此類推,直到模式串完全被匹配,或者主串匹配到最末,結束。演算法如下:
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class KMP{
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str1 = sc.nextLine();//主串
while(sc.hasNext()){
String str2 = sc.nextLine();//模式串
int next[] = new int[str2.length()];
getNext(next,str2);//求解next陣列
System.out.println("next陣列"+java.util.Arrays.toString(next));
List<Integer> pos = new ArrayList<>();//可能存在多個位置起始的字串與模式串匹配,記錄這些在主串中的位置
ifMatch(str1,str2,next,pos);//字串匹配過程
System.out.println("匹配位置:"+pos);//輸出所有匹配的位置
}
}
private static void ifMatch(String str1, String str2, int[] next,List<Integer> pos) {
int j=0;//主串初始位置
int s=0;//匹配串初始位置
while(j<str1.length()){
if(s==-1||str1.charAt(j)==str2.charAt(s)){//比較字元是否相等
j++;
s++;
if(s>=str2.length()){//模式串被完全匹配
pos.add(j-str2.length());
s=0;
j--;
}
}else{
s=next[s];//不等,主串j不變,模式串s變
}
}
}
//next陣列的求解
private static void getNext(int[] next, String str) {
next[0]=-1;
int k=-1;
int j=0;
while(j<str.length()-1){
if(k==-1||str.charAt(j)==str.charAt(k)){
j++;
k++;
next[j]=k;
}else{
k=next[k];
}
}
}
}
結語
或許其中有許多細節不是很明白,也沒有公式的求證,但是很多時候我們應該刪繁去雜,這樣或許才能體會到其中的奧祕。當然每個人有每個人的理解,或許我的理解有誤,歡迎大家拍磚!!!!