1. 程式人生 > 其它 >KMP演算法next陣列

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演算法的優勢:

  1. 主串指標不需要進行回溯(只需要模式串移位即可)
  2. 不匹配時模式串也不一定從初始位置開始(找最大字首字尾長度處開始就行,字首和字尾一致)

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不進行回溯

參考:
超詳細理解:kmp演算法next陣列求解過程和回溯的含義
從頭到尾徹底理解KMP(2014年8月22日版)