模式匹配算法----KMP算法以及next数组的解法
KMP算法:求字符串匹配(也叫模式匹配)的算法,即給定一個字符串,求其某一子串在其中出現的位置。
普通模式匹配
例如:給定字符串為abcabaaabaabcac,求其子串abaabcac在其中出現的位置。
結果為7
對于這種問題,沒有經驗的編程者通常會采用逐個匹配的方法,來得出結果。這就是最簡單一種算法思想。
1. 逐個進行比較,如果相同,就繼續比較下一個,但是我們可以看到下圖中,c與a不相同,這就是所謂的“失配”。
2. 當發生失配,我們會將子字符串逐個后移,直到新的匹配建立,再逐個比較
3.
4.
5.
6.
7.
根據上面的方法,我們可以看到第二步有兩個圖,這是為什么呢?這是因為當出現失配時候,字符串每次后移一個的方法,不能夠立刻建立匹配,還需要繼續后移才能建立。也就是說,這種情況下,后移兩個才能建立匹配。
當然這個例子比較小,只出現了兩次這種情況,但是如果是較大的數據,這種冗余操作是十分低效的。這也就是KMP算法優化的地方。
KMP算法--next[]
KMP算法會創建一個next[]數組,用來保存一個字符失配后,到底跳轉到第幾個的位置才能更快速的建立匹配。這里用了跳轉這個詞,因為并不是向后移動next[i]位,而應該是直接跳轉到相應的位置,使失配位與第next[i]位相對。這個數組是KMP算法的核心。
下面以字符串abaabcac為例,求解next數組,模式匹配的數組下標從1開始(這個算法推薦這么做,很多事情沒有那么多理由)
?
next數組按照最長相等前后綴長度求解:
next[1],字符串“a”,前綴{},后綴{},沒有相等前后綴,next記為0。要注意,前后綴均不包括整個串。
next[2],字符串“ab”,前綴{a},后綴{b},沒有相等前后綴,next記為0。
next[3],字符串“aba”,前綴{a,ab},后綴{a,ba},相等前后綴{a},長度為1,next記為1。
next[4],字符串“abaa”,前綴{a,ab,aba},后綴{a,aa,baa},相等前后綴{a},長度為1,next記為1。
next[5],字符串“abaab”,前綴{a,ab,aba,abaa},后綴{b,ab,aab,baab},相等前后綴{ab},長度為2,next記為2。
next[6],字符串“abaabc”,前綴{a,ab,aba,abaa,abaab},后綴{c,bc,abc,aabc,baabc},相等前后綴{},next記為0。
next[7],字符串“abaabca”,前綴{a,ab,aba,abaa,abaab,abaabc},后綴{a,ca,bca,abca,aabca,baabca},相等前后綴{a},長度為1,next記為1。
next[8],字符串“abaabcac”,前綴{a,ab,aba,abaa,abaab,abaabc,abaabca},后綴{c,ac,cac,bcac,abcac,aabcac,baabcac},相等前后綴{},next記為0。
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| str.charAt(i) | a | b | a | a | b | c | a | c |
| next[i] | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 0 |
由于使用next[]時,每次失配,都需要找它前面一個元素的next[]進行移動,為了方便,我們將next數組右移一位。最左側填充-1,舍棄最右側的元素。
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| str.charAt(i) | a | b | a | a | b | c | a | c |
| next[i] | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 |
為了簡化計算,我們對next[]整體+1,這里得到的是我們通常使用的next[]。
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| str.charAt(i) | a | b | a | a | b | c | a | c |
| next[i] | 0 | 1 | 1 | 2 | 2 | 3 | 1 | 2 |
?
求解next數組代碼:
private static int[] get_Next(String str){ int[] next = new int[str.length()+1]; int j = next[2];// 這里next[1]和next[2]直接賦值next[1] = 0;next[2] = 1;for(int i = 2; i < str.length(); i++) { while(j > 0 && str.charAt(i-1) != str.charAt(j)) {j = next[j]; }if(str.charAt(i-1) == str.charAt(j)) {j++;}next[i] = j;} return next; }KMP算法改進--nextval[]
比較當前next[i]的值,與
1.前兩位必為0,1
計算nextval[3]時,我們可以知道第三位字符位‘a’,next[3]=1,故查看第1位的字符為‘a’,字符相同,所以nextval[3]=next[1]=0
計算nextval[4]時,我們可以知道第四位字符位‘a’,next[4]=2,故查看第2位的字符為‘b’,字符不同,所以nextval[4]=next[4]=2
計算nextval[5]時,我們可以知道第五位字符位‘b’,next[5]=2,故查看第2位的字符為‘b’,字符相同,我們知道next[2]=1,故繼續查看第1位的字符為‘a’,字符不同,所以nextval[5]=next[2]=1
計算nextval[6]時,我們可以知道第六位字符位‘c’,next[6]=3,故查看第3位的字符為‘a’,字符不同,所以nextval[6]=next[6]=3
計算nextval[7]時,我們可以知道第七位字符位‘a’,next[7]=1,故查看第1位的字符為‘a’,字符相同,所以nextval[7]=next[1]=0
計算nextval[8]時,我們可以知道第八位字符位‘c’,next[8]=2,故查看第2位的字符為‘b’,字符不同,所以nextval[7]=next[7]=2
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| str.charAt(i) | a | b | a | a | b | c | a | c |
| nextval[i] | 0 | 1 | 0 | 2 | 1 | 3 | 0 | 2 |
根據next數組,我們再來做一遍這個題目:給定字符串為abcabaaabaabcac,求其子串abaabcac在其中出現的位置。
?
1.創建匹配,第4個字符發生失配
2.使失配位與子串的第next[4]=2位相對,但是這是第二個字符又失配了
3.使失配位與子串的第next[2]=1位相對,此時第一個字符就失配
4.使失配位與子串的第next[1]=0位相對,注意我們的next數組是從1開始算的,所以相當于整體后移一個單位,下圖的失配位實際是"c"
5.使失配位與子串的第next[4]=2位相對
6.使失配位與子串的第next[4]=2位相對
7.匹配成功
完整代碼如下:
public class Main { public static void main(String[] args) { String str = "abaabcac"; String orig ="aabaabcac"; int[] next = get_Next(str); for (int i = 1; i < next.length; i++) {System.out.print(next[i] + " ");}System.out.println();search(orig, str, next); } //next[i]表示的是str的"部分匹配表",這個表表示的是str前綴與后綴的最長公共字符串的長度private static int[] get_Next(String str){ int[] next = new int[str.length()+1]; int j = next[2];// 這里next[1]和next[2]直接賦值next[1] = 0;next[2] = 1;// 第2位之后的next,為什么從2開始,不是已經賦值了嗎?這在于str.charAt(0)為字符串的第一個字母for(int i = 2; i < str.length(); i++) { while(j > 0 && str.charAt(i-1) != str.charAt(j)) {j = next[j]; }if(str.charAt(i-1) == str.charAt(j)) {j++;}next[i] = j;} return next; } //orig為主串,而find為模式串,查找匹配位置以及匹配長度 private static void search(String orig, String find, int[]next){ int j = next[0]; for(int i = 0;i < orig.length(); i++){ while(j > 0 && orig.charAt(i) != find.charAt(j)) j = next[j]; if(orig.charAt(i) == find.charAt(j)){ j++; }if(j == find.length()){ System.out.println("find at position " + (i - j+1)); System.out.println(orig.subSequence(i - j + 1, i + 1)); j = next[j];}}} }當然還有一種BM算法效率比KMP算法還好。有興趣的讀者可以參考時空權衡在模式匹配算法中的應用(JAVA)--Horspool算法(簡化版BM算法)
nextval數組實際的數值是明顯要小于next數組的,nextval數組由于考慮到了移動到的位置的數與當前位置數的關系,可以減少移動的距離。
?
?
總結
以上是生活随笔為你收集整理的模式匹配算法----KMP算法以及next数组的解法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CPython 和IronPython的
- 下一篇: 蓝桥杯算法提高----2n皇后