一、KMP算法
1、KMP算法实现(p500):
public class KMP { private String pat; private int[][] dfa;//确定有限状态自动机 public void dfa(String pat)//构造DFA { this.pat = pat; int M = pat.length(); int R = 256; dfa = new int[R][M]; dfa[pat.charAt(0)][0] = 1; for (int X = 0, j = 1; j < M; j++) { for (int c = 0; c < R; c++) dfa[c][j] = dfa[c][X]; dfa[pat.charAt(j)][j] = j + 1; X = dfa[pat.charAt(j)][X]; } } public int search(String txt) { int i,j,M = pat.length(),N = txt.length(); for (i = 0, j = 0; i < N && j < M; i ++) j = dfa[txt.charAt(i)][j]; if (j == M) return i - M; else return N; } public static void main(String[] args) { String pat = "ABABAC"; String txt = "ABABCABABAC"; KMP kmp = new KMP(); kmp.dfa(pat); System.out.println(kmp.search(txt)); // TODO Auto-generated method stub } }
2、对于长度为M的模式字符串和长度为N的文本,KMP字符串查找算法访问的字符不会超过M+N个。
3、KMP算法由于不回退,适用于长度不确定的输入流中进行查找,以及在重复性很高的文本中查找重复性很高的模式。
二、Boyer-Moore字符串查找算法(启发式的处理不匹配字符)
1、实现(p504)
public class BoyerMoore//启发式处理不匹配字符 { private String pat; private int[] right;//记录每个字符在模式中出现的最右位置 BoyerMoore(String pat) { this.pat = pat; int R = 256; int M = pat.length(); right = new int[R]; for (int c = 0; c < R; c++) right[c] = -1; for (int j = 0; j < M; j++) right[pat.charAt(j)] = j; } public int search(String txt) { int N = txt.length(); int M = pat.length(); int skip; for (int i = 0; i <= N-M; i += skip) { skip = 0; for (int j = M - 1; j >= 0; j--) if (txt.charAt(i + j) != pat.charAt(j)) { skip = j - right[txt.charAt(i+j)]; if (skip < 0) skip = 1; break; } if (skip == 0) return i; } return N; } public static void main(String[] args) { // TODO Auto-generated method stub String pat = "ABABAC"; String txt = "ABABCABABAC"; BoyerMoore bm = new BoyerMoore(pat); System.out.println(bm.search(txt)); } }
2、在一般情况下,对于长度为N的文本和长度为M的模式字符串,使用了Boyer-Moore的子字符串查找算法通过启发式处理不匹配的字符需要~N/M次字符串比较。
三、Rabin-Krap指纹字符串查找算法(基于散列的字符串查找算法)
1、实现(p508):
public class RabinKarp { private String pat; private int R = 256; private long Q; private long RM; private long patHash; private int M; public RabinKarp(String pat) { this.pat = pat; this.M = pat.length(); Q = 97; RM = 1; for (int i = 1; i <= M - 1; i++) RM = (RM * R) % Q; patHash = hash(pat, M); } private long hash(String pat, int M) { long h = 0; for (int i = 0; i < M; i++) { h = (R * h + pat.charAt(i)) % Q; } return h; } public boolean check(int i) { return true; } public int search(String txt) { int N = txt.length(); long txtHash = hash(txt, M); if (patHash == txtHash&& check(0)) return 0; for (int i = M; i < N; i++) { txtHash = (txtHash + Q - RM * txt.charAt(i-M) % Q) % Q; txtHash = (txtHash * R + txt.charAt(i)) % Q; if (patHash == txtHash && check(i - M + 1)) return i - M + 1; } return N; } public static void main(String[] args) { // TODO Auto-generated method stub String pat = "ABABAC"; String txt = "ABABCABABAC"; RabinKarp rk = new RabinKarp(pat); System.out.println(rk.search(txt)); } }2、使用蒙特卡洛算法的Rabin-Karp子字符串查找算法的运行时间是线性级别且出错概率极小;使用拉斯维加斯算法的Rabin-Karp算法能够保证正确性且性能及其接近线性级别。
四、各种字符串查找算法实现成本总结:
算法 |
版本 | 最坏情况操作次数 | 一般情况操作次数 | 在文中是否回退 | 正确性 | 格外空间需要 |
Knuth-Morris-Pratt | 完整的DFA | 2N | 1.1N | 否 | 是 | MR |
Boyer-Moore算法 | 启发式查找不匹配的字符 | MN | N/M | 是 | 是 | R |
Rabin-Krap算法 | 蒙特卡洛算法 拉斯维加斯算法 |
7N 7N* |
7N | 否 是 |
是 | 1 |
暴力算法 | MN | 1.1N | 是 | 是 | 1 |