二维模式(矩阵)匹配(Rabin-Karp算法推广到二维)[转]
本文著重討論由Rabin-Karp算法推廣到二維來解決二維模式匹配問題的算法。
問題:
在一個n1*n2的二維字符組成中搜尋一個給定的m1*m2的模式。參考《算法導論》習題32.2-3.
分析:
1. 首先簡單介紹一下Rabin-Karp算法
Rabin-Karp算法是一種字符串匹配算法,它的主要思想是預先計算出模式串的hash值,匹配時再計算出待匹配子串的hash值,直接比較模式串和當前子串的hash值是否相等即可判斷是否匹配。
為了便于說明,以下以數字串為例(字符串的每個字符都是一個十進制的數字,比如字符串31415)。已知一個模式P[1..m],設p表示其相應的 十進制數的值。類似的,對于給定的文本T[1..n],用ts表示其長度為m的子字符串T[s+1..s+m](s=0,1,..,n-m)相應的十進制 數的值。很顯然的,如果T[s+1..s+m]與模式P[1..m]匹配,那么ts一定等于p,相反的,如果ts=p,那么T[s+1..s+m]一定與P[1..m]匹配。如果能夠在O(m)時間內計算出p的值,并在總共O(n-m+1)的時間內計算出所有ts的值,那么通過把p值與每個ts值進行比較,就能夠在O(m)+O(n-m+1)=O(n)的時間內,找到所有的匹配。
RK算法通過霍納法則(Horner’s Rule)在O(m)時間內計算出p的值:
p=P[m]+10(P[m-1]+10(P[m-2]+…+10(P[2]+10P[1])..))
同理可以在O(m)時間內計算出t0的值。
又因為
ts+1=10[ts-10m-1T[s+1])+T[s+m+1]
所以可以在常數時間內根據ts計算出ts+1
例如,如果m=5,ts=12345,假定下一位是6,去掉高位數字1,再加入低位數字6,就得到:
ts+1=10*(12345-10000*1)+6=23456
因此,Rabin-Karp算法能夠用O(m)的預處理時間和O(n-m+1)的匹配時間,找出模式P[1..m]在文本T[1..n]中的所有出現。
這個過程唯一的問題是p和ts的值可能太大,RK算法是通過模等價來處理。實際計算出來的p和ts的值都是模q后的值,在匹配時如果p和ts值相等,串還未必匹配,這時候還需要通過樸素的比較這兩個串來測試。本文不考慮這個,有興趣的可以參考算法導論。
另外,這個例子是基于數字串的,對于一般情況,可以假定每個字符都是基數為d的表示法中的一個字符,這時上面2個計算公式中的10要換成相應的d。
2. 推廣到二維
首先,來看模式矩陣。如果把m2列中的每一列都看做一個整體,那么他們每一個都是一個一維的串,可以分別計算出hash值(使用霍納法則),這樣模式矩陣就成了一個一維的長度為m2的模式串。
然后,對大矩陣的前m1行,用同樣的方法能得到一個長度為n2的串。
這樣,在大矩陣的前m1行中尋找模式矩陣,就轉化成了一維的字符串匹配問題。(這里使用一維的串匹配算法就能解決,比如KMP)
最后,用同樣的方法,在大矩陣的第2到第m1+1行,第3到m1+2行。。。都可以用同樣的方法匹配。
這里的關鍵是,每次匹配時,轉化后的一維串可以通過上次的串直接計算出來。(類似于Rabin-Karp由ts可以在常數時間內計算出ts+1)
源碼- JAVA
01 public class StringMatch2D {02
03 public static void main(String[] args) {
04 char[][] text = {
05 { 'a', 'b', 'a', 'b', 'a' },
06 { 'a', 'b', 'a', 'b', 'a' },
07 { 'a', 'b', 'b', 'a', 'a' },
08 { 'a', 'b', 'a', 'a', 'b' },
09 { 'b', 'b', 'a', 'b', 'a' }
10 };
11 char[][] pattern = {
12 { 'a', 'b' },
13 { 'b', 'a' }
14 };
15
16 matrixPatternMatch(text, pattern);
17 }
18
19 private static void matrixPatternMatch(char[][] text, char[][] pattern) {
20 // pre-process
21 int[] patternStamp = new int[pattern[0].length];
22 int[] textStamp = new int1.length];
23
24 caculateStamp(pattern, pattern.length, patternStamp);
25 caculateStamp(text, pattern.length, textStamp);
26
27 int[] next = new int[patternStamp.length];
28 caculateNext(patternStamp, next);
29
30 for (int i = 0; i < (text.length - pattern.length + 1); i++) {
31 int col = isMatch(patternStamp, textStamp, next);
32 if (col != -1) {
33 System.out.println("found");
34 System.out.println(i+", "+col);
35 }
36
37 // move down
38 if(i < text.length - pattern.length)
39 caculateNextStamp(text, pattern.length, textStamp, i);
40 }
41
42 }
43
44 private static int isMatch(int[] patternStamp, int[] textStamp, int[] next) {
45 int i = 0, j = 0;
46 while (j < patternStamp.length && i < textStamp.length) {
47 if (j == -1 || patternStamp[j] == textStamp[i]) {
48 i++;
49 j++;
50 } else {
51 j = next[j];
52 }
53 }
54
55 if (j == patternStamp.length) {
56 return i-j;
57 } else {
58 return -1;
59 }
60 }
61
62 private static void caculateNext(int[] pattern, int[] next) {
63 next[0] = -1;
64
65 int i = 0, j = -1;
66 while(i<pattern.length-1) {
67 if(j==-1 || pattern[i] == pattern[j]) {
68 i++;
69 j++;
70 next[i] = j;
71 } else {
72 j = next[j];
73 }
74 }
75
76 }
77
78 private static void caculateNextStamp(char[][] text, int height,
79 int[] textStamp, int row) {
80 int d = (int) Math.pow(26, height-1);
81 for (int i = 0; i < textStamp.length; i++) {
82 textStamp[i] = 26 * (textStamp[i] - d * text[row][i]) + text[row + height][i];
83 }
84 }
85
86 private static void caculateStamp(char[][] input, int height, int[] result) {
87 for (int i = 0; i < result.length; i++) {
88 result[i] = 0;
89 for (int j = 0; j < height; j++) {
90 result[i] = 26 * result[i] + input[j][i];
91 }
92 }
93 }
94
95 }
第21-28行,進行匹配前的預處理。
第21-22行,patternStamp和textStamp分別用來存儲模式矩陣以及文本矩陣的前m1行轉換成一維數字串。
第86-93行,定義函數caculateStamp,輸入是一個二維字符矩陣,輸出是轉換成的一維數字串。對輸入矩陣的每一列用霍納法則計算出一個值,作為轉換后的一維數字串的一位。
第24-25行,用函數caculateStamp計算得到轉換后的一維數字串。
第27-28行,計算轉換后的一維模式串的next數組,用于KMP算法進行一維串匹配。
第30-40行,進行實際的匹配。
匹配時,對文本矩陣每m1行進行一次匹配,匹配前都轉換成一維的數字串,用KMP算法(第31行)進行一維串匹配,總共要匹配n1-m1+1次。
第38-39行,待匹配的文本矩陣下移一行,計算轉換成的新一維數字串。
第78-84行,函數caculateNextStamp,用來根據上一次的一維轉換數字串計算出新的,類似于RK算法中的公式二。
時間復雜度:
預處理 – O(n2*m1)
計算模式矩陣的stamp O(m1*m2) + 計算文本矩陣前m1行的stamp O(n2*m1) + 計算模式矩陣stamp的next數組 O(m2)
匹配 – O((n1-m1+1)*n2)
總共 n1-m1+1次
每次用KMP算法匹配 O(n2) + 計算文本矩陣下m行的stamp O(n2)
總的時間復雜度 – O(n1*n2)
[轉]: http://www.slimeden.com/2010/10/algorithm/matrixpatternmatching
總結
以上是生活随笔為你收集整理的二维模式(矩阵)匹配(Rabin-Karp算法推广到二维)[转]的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: etherchannel
- 下一篇: 谷歌发安全警告:社交网络威胁用户隐私