字串匹配演算法總結 (分析及Java實現)
字串模式匹配演算法(string searching/matchingalgorithms)
顧名思義,就是在一個文字或者較長的一段字串中,找出一個或多個指定字串(Pattern),並返回其位置。這類演算法屬基礎演算法,各種程式語言都將其包括在自帶的String類函式中,而且由之衍生出來的正則表示式也是必須掌握的一種概念和程式設計技術。
Brute-Force演算法
其思路很簡單:從目標字串初始位置開始,依次分別與Pattern的各個位置的字元比較,如相同,比較下一個位置的字元直至完全匹配;如果不同則跳到目標字串下一位置繼續如此與Pattern比較,直至找到匹配字串並返回其位置。
我們注意到Brute Force 演算法是每次移動一個單位,一個一個單位移動顯然太慢,設目標串String的長度為m,Pattern的長度為n,不難得出BF演算法的時間複雜度最壞為O(mn),效率很低。
程式碼也很簡單,如下所示(Java)。不過,下面的程式碼有優化,例如21行的總的迴圈次數是m – n, 33行的不匹配迴圈終止,都讓時間複雜度大為降低。
1./** 2.* Brute-Force演算法 3.* 4.* @author stecai 5.*/ 6.publicclass BruteForce { 7. 8./** 9.* 找出指定字串在目標字串中的位置 10.* 11.* @param source 目標字串 12.* @param pattern 指定字串 13.* @return指定字串在目標字串中的位置 14.*/ 15.publicstaticint match(String source, String pattern) { 16.int index = -1; 17.boolean match = true; 18. 19.for (int
i = 0, len = source.length() - pattern.length(); i <=
len 20.match = true; 21. 22.for (int j = 0; j < pattern.length(); j++) { 23.if (source.charAt(i + j) != pattern.charAt(j)) { 24.match = false; 25.break; 26.} 27.} 28. 29.if (match) { 30.index = i; 31.break; 32.} 33.} 34. 35.return index; 36.} 37.} |
KMP演算法
KMP演算法是一種改進的字串匹配演算法,關鍵是利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。在BF演算法的基礎上使用next函式來找出下一次目標函式與Pattern比較的位置,因為BF演算法每次移動一位的比較是冗餘的,KMP利用Pattern字元重複的特性來排除不必要的比較,從而可以每次移動n位來排除冗餘。對於Next函式近似接近O(m),KMP演算法的時間複雜度為O(n),所以整個演算法的時間複雜度為O(n+m)。
例如:模式pattern,文字string。
String: ABCADCACBAB
在紅色字型處發生失配,按照傳統演算法,應當從第二個字元B對齊再進行匹配,這個過程中,對字串String的訪問發生了“回朔”。我們不希望發生這樣的回朔,而是試圖通過儘可能的“向右滑動”模式串next陣列對應位置的值,讓Pattern中B字元對齊到String中D的字。
Pattern:ABCAC
String: ABCADCACBAB
因此,問題的關鍵是計算向右引動的串的模式值next[]。模式串開始為值(既next[0])為-1,後面的任一位置例如j,計算j之前(既0 ~ j-1)中最大的相同的前後綴的字元數量,即為next陣列j位置的值。例如:
位置 j |
0 |
1 |
2 |
3 |
4 |
5 |
模式串 |
A |
B |
C |
A |
B |
D |
next[] |
-1 |
0 |
0 |
0 |
1 |
2 |
從上表可以看出, 3位置之前,字首和字尾沒有相同的,所以值為0;4位置之前有最大前後綴A,長度為1,所以值為1,5之前有最大前後綴AB,長度為2,所以值為2。
KMP雖然經典,很不容易理解,即使理解好了,編碼也相當麻煩!特別是計算next陣列的部分。程式碼如下所示,核心是next[]陣列的得出方法:
1./** 2.* KMPSearch 演算法 3.* 4.* @author stecai 5.*/ 6.publicclass KMPSearch { 7./** 8.* 獲得字串的next函式值 9.* 10.* @param str 11.* @return next函式值 12.*/ 13.privatestaticint[] calculateNext(String str) { 14.int i = -1; 15.int j = 0; 16.int length = str.length(); 17.int next[] = newint[length]; 18.next[0] = -1; 19. 20.while (j < length - 1) { 21.if (i == -1 || str.charAt(i) == str.charAt(j)) { 22.i++; 23.j++; 24.next[j] = i; 25.} else { 26.i = next[i]; 27.} 28.} 29. 30.return next; 31.} 32. 33./** 34.* KMP匹配字串 35.* 36.* @param source 目標字串 37.* @param pattern 指定字串 38.* @return若匹配成功,返回下標,否則返回-1 39.*/ 40.publicstaticint match(String source, String pattern) { 41.int i = 0; 42.int j = 0; 43.int input_len = source.length(); 44.int kw_len = pattern.length(); 45.int[] next = calculateNext(pattern); 46. 47.while ((i < input_len) && (j < kw_len)) { 48.// 如果j = -1,或者當前字元匹配成功(即S[i] == P[j]),都令i++,j++ 49.if (j == -1 || source.charAt(i) == pattern.charAt(j)) { 50.j++; 51.i++; 52.} else { 53.// 如果j != -1,且當前字元匹配失敗(即S[i] != P[j]),則令 |