▶ 书中第五章部分程序,包括在加上自己补充的代码,Knuth-Morris-Pratt 无回溯匹配,Boyer - Moore 无回溯匹配,Rabin - Karp 指纹匹配
● Knuth-Morris-Pratt 无回溯匹配
1 package package01; 2 3 import edu.princeton.cs.algs4.StdOut; 4 5 public class class01 6 { 7 private final int R; // 字符集基数 8 private int[][] dfa; // 回溯数组 9 private String pat; // 模式的两种保存方式 10 private char[] pattern; 11 12 public class01(String pat) // 分别从两种表示方式中创建回溯数组,算法仙童 13 { 14 this.R = 256; 15 this.pat = pat; 16 17 int m = pat.length(); 18 dfa = new int[R][m]; 19 dfa[pat.charAt(0)][0] = 1; // 第 0 位匹配,接下来匹配第 1 位 20 for (int x = 0, j = 1; j < m; j++) // 跳转点初始化为 0,即某次比较后需要退回到模式的头部再开始 21 { 22 for (int c = 0; c < R; c++) 23 dfa[c][j] = dfa[c][x]; // 原文字母 c 与模式第 j 位比较等价于原文字母 c 与模式第 x 位比较,跳转点也相同 24 dfa[pat.charAt(j)][j] = j + 1; // 原文某位与模式第 j 位正确匹配,接下来应该用原文下一位与模式 j+1 位进行匹配 25 x = dfa[pat.charAt(j)][x]; // 设置 x 为原文某位与模式第 x 位比较后的跳转点 26 } 27 } 28 29 public class01(char[] pattern, int R) 30 { 31 this.R = R; 32 this.pattern = new char[pattern.length]; 33 for (int j = 0; j < pattern.length; j++) 34 this.pattern[j] = pattern[j]; 35 36 int m = pattern.length; 37 dfa = new int[R][m]; 38 dfa[pattern[0]][0] = 1; 39 for (int x = 0, j = 1; j < m; j++) 40 { 41 for (int c = 0; c < R; c++) 42 dfa[c][j] = dfa[c][x]; 43 dfa[pattern[j]][j] = j + 1; 44 x = dfa[pattern[j]][x]; 45 } 46 } 47 48 public int search(String txt) // 利用 DFA 原理进行匹配 49 { 50 int m = pat.length(), n = txt.length(), i, j; // i 指向待匹配字符串,j 指向模式 51 for (i = 0, j = 0; i < n && j < m; i++) // i 不断自增,j 不断跳转 52 j = dfa[txt.charAt(i)][j]; // y = dfa[txt[i]][j] 表示 txt[i] 与 pattern[j] 比较后,下一步应该与 txt[i+1] 进行比较的模式的下标, 53 // 即下一步应该把 txt[i+1] 与 pattern[y] 进行比较 54 return (j == m) ? (i - m) : n; // 找到匹配则返回子字符串的索引位置,没有匹配则返回原字符串的长度 55 } 56 57 public int search(char[] text) 58 { 59 int m = pattern.length; 60 int n = text.length; 61 int i, j; 62 for (i = 0, j = 0; i < n && j < m; i++) 63 j = dfa[text[i]][j]; 64 return (j == m) ? (i - m) : n; 65 } 66 67 public static void main(String[] args) 68 { 69 String pat = args[0], txt = args[1]; 70 char[] pattern = pat.toCharArray(), text = txt.toCharArray(); // 输入存成两种格式,分别进行匹配 71 72 class01 kmp1 = new class01(pat); 73 int offset1 = kmp1.search(txt); 74 class01 kmp2 = new class01(pattern, 256); 75 int offset2 = kmp2.search(text); 76 77 StdOut.println("text: " + txt); // 输出原文 78 StdOut.print("pattern: "); 79 for (int i = 0; i < offset1; i++) // 匹配的前导空格 80 StdOut.print(" "); 81 StdOut.println(pat); 82 83 StdOut.print("pattern: "); 84 for (int i = 0; i < offset2; i++) 85 StdOut.print(" "); 86 StdOut.println(pat); 87 } 88 }
● Boyer - Moore 无回溯匹配
1 package package01; 2 3 import edu.princeton.cs.algs4.StdOut; 4 5 public class class01 6 { 7 private final int R; 8 private int[] right; // 回溯控制数组 9 private char[] pattern; 10 private String pat; 11 12 public class01(String inputPat) 13 { 14 R = 256; 15 pat = inputPat; 16 int m = pat.length(); 17 right = new int[R]; 18 for (int c = 0; c < R; c++) // 初始化为 -1 19 right[c] = -1; 20 for (int j = 0; j < m; j++) // 记录每个字母在模式或者能够出现的最靠右的地方 21 right[pat.charAt(j)] = j; 22 } 23 24 public class01(char[] inputPattern, int inputR) 25 { 26 R = inputR; 27 pattern = new char[inputPattern.length]; 28 for (int j = 0; j < pattern.length; j++) 29 pattern[j] = inputPattern[j]; 30 int m = pattern.length; 31 right = new int[R]; 32 for (int c = 0; c < R; c++) 33 right[c] = -1; 34 for (int j = 0; j < m; j++) 35 right[pattern[j]] = j; 36 } 37 38 public int search(String txt) 39 { 40 int m = pat.length(), n = txt.length(), skip; 41 for (int i = 0; i <= n - m; i += skip) // i 指向原文,向后移动 42 { 43 skip = 0; 44 for (int j = m - 1; j >= 0; j--) // j 指向模式,向前移动 45 { 46 if (pat.charAt(j) != txt.charAt(i + j)) // 若原文第 i+j 位 和模式第 j 位匹配,则自减 j 继续检查,若不匹配则要尝试跳转 47 { 48 skip = Math.max(1, j - right[txt.charAt(i + j)]); // 考虑原文第 i+j 位在匹配串中最右出现的位置, 49 break; // 若模式根本没有该字符,则 right[x] 等于 -1,skip 等于 1,原文前进 1 格,从模式头部开始重新匹配 50 } // 若模式有该字符,则 j - right[x] 等于该字符的右边的字符数(跳转后不需要检查的部分) 51 } 52 if (skip == 0) // j 循环检查完了 skip 都没变过,找到了,此时 i 指向原文中匹配的子字符串的开头 53 return i; 54 } 55 return n; // i 循环走完了都没找到 56 } 57 58 public int search(char[] text) 59 { 60 int m = pattern.length, n = text.length, skip; 61 for (int i = 0; i <= n - m; i += skip) 62 { 63 skip = 0; 64 for (int j = m - 1; j >= 0; j--) 65 { 66 if (pattern[j] != text[i + j]) 67 { 68 skip = Math.max(1, j - right[text[i + j]]); 69 break; 70 } 71 } 72 if (skip == 0) 73 return i; 74 } 75 return n; 76 } 77 78 public static void main(String[] args) 79 { 80 String pat = args[0], txt = args[1]; 81 char[] pattern = pat.toCharArray(), text = txt.toCharArray(); 82 83 class01 bm1 = new class01(pat); 84 int offset1 = bm1.search(txt); 85 class01 bm2 = new class01(pattern, 256); 86 int offset2 = bm2.search(text); 87 88 StdOut.println("text: " + txt); 89 StdOut.print("pattern: "); 90 for (int i = 0; i < offset1; i++) 91 StdOut.print(" "); 92 StdOut.println(pat); 93 94 StdOut.print("pattern: "); 95 for (int i = 0; i < offset2; i++) 96 StdOut.print(" "); 97 StdOut.println(pat); 98 } 99 }
● Rabin - Karp 指纹匹配
1 package package01; 2 3 import java.math.BigInteger; 4 import java.util.Random; 5 import edu.princeton.cs.algs4.StdOut; 6 7 public class class01 8 { 9 private String pat; 10 private long patHash; // 模式的 hash 值 11 private int R; // 字符集基数 12 private int m; // 模式畅读 13 private long q; // 计算 hash 用的大素数 14 private long RM; // 中间值,等于 R^(m-1) % q 15 16 public class01(char[] inputPattern, int inputR) 17 { 18 new class01(String.valueOf(inputPattern), inputR); 19 } 20 21 public class01(String inputPat, int inputR) 22 { 23 pat = inputPat; 24 R = inputR; 25 m = pat.length(); 26 q = longRandomPrime(); 27 RM = 1; // 计算 RM 28 for (int i = 1; i <= m - 1; i++) 29 RM = (R * RM) % q; 30 patHash = hash(pat, m); 31 } 32 33 private long hash(String key, int m) // 计算 hash 值,m 为字符串长度,hash = (Σ key[i] * R^(m-1-i)) % q,求和循环 i 34 { // 递推 x[0] = key[0] % q,x[k+1] = (R * x[k] + key[k+1]) % q 35 long h = 0; 36 for (int j = 0; j < m; j++) 37 h = (R * h + key.charAt(j)) % q; 38 return h; 39 } 40 41 public int search(String txt) 42 { 43 int n = txt.length(); 44 if (n < m) // 原文太短 45 return n; 46 long txtHash = hash(txt, m); // 注意传入模式长度 m,表示计算 txt[0] ~ txt[m-1] 的 hash 47 if ((patHash == txtHash) && check(txt, 0)) // 原文头一段 hash 等于模式 hash,且通过了逐字比较,完成匹配 48 return 0; 49 for (int i = m; i < n; i++) 50 { 51 txtHash = (txtHash - RM * txt.charAt(i - m) % q + q) % q; // 更新原文的 hash,首先去掉第 i - m 位 52 txtHash = (txtHash*R + txt.charAt(i)) % q; // 然后加入第 i 位 53 int offset = i - m + 1; // 新的比较起点 54 if ((patHash == txtHash) && check(txt, offset)) // 同上,通过 hash 比较和逐字比较,完成匹配 55 return offset; 56 } 57 return n; // 没有匹配 58 } 59 60 private static long longRandomPrime() // 生成大素数 61 { 62 return BigInteger.probablePrime(31, new Random()).longValue(); 63 } 64 65 private boolean check(String txt, int i) // 复查原文 txt[i] ~ txt[i+m-1] 是否匹配模式 66 { 67 for (int j = 0; j < m; j++) 68 { 69 if (pat.charAt(j) != txt.charAt(i + j)) 70 return false; 71 } 72 return true; 73 } 74 75 public static void main(String[] args) 76 { 77 String pat = args[0], txt = args[1]; 78 class01 searcher = new class01(pat, 256); 79 int offset = searcher.search(txt); 80 81 StdOut.println("text: " + txt); 82 StdOut.print("pattern: "); 83 for (int i = 0; i < offset; i++) 84 StdOut.print(" "); 85 StdOut.println(pat); 86 } 87 }