优势:java
缺点:c++
算法 | 版本 | 最坏状况 | 通常状况 | 是否回退 | 正确性 | 额外空间需求 |
暴力算法 | -- | MN | 1.1N | 是 | 是 | 1 |
KMP算法 | 完整的DFA | 2N | 1.1N | 否 | 是 | MR |
Boyer-Moore算法 | 启发式查找不匹配字符 | MN | N/M | 是 | 是 | R |
Rabin-Karp算法 | 蒙特卡洛算法 | 7N | 7N | 否 | 是* | 1 |
拉斯维加斯算法 | 7N* | 7N | 是 | 是 | 1 |
设文本长度为N,要匹配的模式的长度为M,暴力查找算法在最坏的状况下运行时间与MN成正比,但在处理许多应用程序中的字符串时,它的实际运行时间通常与M+N成正比。算法
使用一个值指针i跟踪文本,一个指针j跟踪要匹配的模式,对每个i,代码首先将j重置为0并不断增大,直到找到了一个不匹配的字符或者是匹配成功(j==M)。数组
public static int search(String pat, String txt) { int M = pat.length(); int N = txt.length(); for(int i = 0;i<= N;i++) { int j; for(j=0;j<M;j++) if(txt.charAt(i+j)!=pat.charAt(j)) break; if(j==M) return i; } return N; }
一样使用一个值指针i跟踪文本,一个指针j跟踪要匹配的模式,在i和j指向的字符匹配时,i和j同时后移一位。若是i和j字符不匹配,那么须要回退这两个指针,j指向模式的开头,i指向此次匹配开头的下一个字符。dom
public static int Search(String pat,String txt) { int j, M = pat.length(); int i, N = txt.length(); for(i=0,j=0;i<N&&j<M;i++) { if(txt.charAt(i)==pat.charAt(j)) j++; else {i-=j;j=0;} } if(j==M) return i-M; else return N; }
当出现不匹配时,就能知晓一部份内容(由于匹配失败以前的字符已经和模式相匹配)。能够利用这些信息避免指针回退。使人惊讶的是,KMP算法在匹配失败时,总能将j设置为一个值以使i不回退。函数
在KMP算法中,不会回退文本指针i,而是用一个数组dfa[][]来记录匹配失败时指针j应该回退多远。对于每个字符c,在比较了c和pat.charAt(j)后,dfa[c][j]表示的是应该和下一个文本字符比较的模式字符的位置。在匹配时会继续比较下一个字符,所以dfa[pat.charAt(j)][i]老是j+1; 在不匹配时,不只能够知道txt.charAt(i)的字符,还能够知道正文中前j-1个字符--它们就是模式中的前j-1个字符。性能
public class KMP { private final int R; private int[][] dfa; private String pat; //构造函数中生成dfa[][]数组 public KMP(String pat) { this.R = 256; this.pat = pat; int m = pat.length(); 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]; } } //search()方法经过dfa[][]中计算的值来查找子字符串 public int search(String txt) { int m = pat.length(); int n = txt.length(); int i, j; for (i = 0, j = 0; i < n && j < m; i++) { j = dfa[txt.charAt(i)][j]; } if (j == m) return i - m; return n; } }
对于长度为M的模式字符串和长度为N的文本,KMP子字符串查找算法访问的字符串不会超过M+N个。this
Boyer-Moore算法是一种从右向左扫描模式字符串并将它与文本匹配的算法。spa
有文本FINDINAHAYSTACKNEEDLE和模式字符串NEEDLE. 由于是从右向左扫描,因此会先比较模式中最后一位E和文本中下标为5的N。不匹配,由于模式字符串中也出现了N,则右移模式字符串使得模式中最右边的N(这里是位置0的N)与文本中的相应N对齐。而后接着比较模式字符串最后的E和文本中的S(下标10),不匹配,并且模式中不含有字符S,能够将模式直接右移6位,而后继续匹配......指针
上述方法被称为启发式的处理不匹配字符。要实现之,须要一个数组right[]保存字母表中每一个字母在模式字符串中出现的最靠右的下标(若是不存在则为-1)。这个值揭示了若是发生不匹配,应该右跳跃多远。
在right[]数组计算后,算法实现起来就很是容易了。用一个索引i在文本中从左向右移动,用索引j在模式字符串中从右向左移动。内循环检查检查正文和模式字符串在位置i是否相等,若是从M-1到0的全部j,txt.charAt(i+j)都和pat.charAt(j)相等,就是找到了匹配。不然匹配失败,失败有三种状况:
在通常状况下,对于长度为N的文本和长度为M的模式字符串,该方法经过启发式处理不匹配的字符须要~N/M次比较。
public class BoyerMoore { private final int R; private int[] right; private String pat; public BoyerMoore(String pat) { this.R = 256; this.pat = pat; right = new int[R]; for (int c = 0; c < R; c++) right[c] = -1; for (int j = 0; j < pat.length(); j++) right[pat.charAt(j)] = j; } public int search(String txt) { int m = pat.length(); int n = txt.length(); int skip; for (int i = 0; i <= n - m; i += skip) { skip = 0; for (int j = m-1; j >= 0; j--) { if (pat.charAt(j) != txt.charAt(i+j)) { skip = Math.max(1, j - right[txt.charAt(i+j)]); break; } } if (skip == 0) return i; } return n; } }
Rabin-Karp算法是一种基于散列的子字符串查找算法--先计算模式字符串的散列值,而后用相同的散列函数计算文本中全部可能的M个字符的子字符串的山裂纸并与模式字符串的散列值比较。若是二者相同,再继续验证二者是否匹配。
长度为M的对应着一个R进制的M位数,
举例说明Rabin-Karp算法:例如要在文本3141592653589793中找到模式26535,首先选择散列表大小Q(这里设置为997),采用除留余数法,散列值为26535%997 = 613,而后计算文本中全部长度为5的字符串的散列值并寻找匹配。
实现Rabin-Karp算法关键是要找到一种方法可以快速地计算出文本中全部长度等于要匹配字符串长度的子字符串的散列值。也就是对全部位置i, 高效计算出文本中i+1位置的子字符串的值。具体算法为:假设已知h(xi) = xi mod Q, 将模式字符串右移一位等价于将xi替换为x(i+1), x(i+1)等于xi减去第一个数字的值,乘以R,再加上最后一个数字的值。这么作的结果是不管M是五、100仍是1000,均可以在常数时间内不断地一格一格向后移动。
对于5位的数,能够用int直接计算,但若是M等于100、1000就不行了。这时候可使用Horner方法。其计算方法以下:
private long hash(String key, int m) { long h = 0; for (int j = 0; j < m; j++) h = (R * h + key.charAt(j)) % q; return h; }
有两种表明实现:蒙特卡洛方法和拉斯维加斯方法。
public class RabinKarp { private String pat; private long patHash; private int m; private long q; private int R; private long RM; public RabinKarp(String pat) { this.pat = pat; R = 256; m = pat.length(); q = longRandomPrime(); RM = 1; for (int i = 1; i <= m-1; i++) RM = (R * RM) % q; patHash = hash(pat, m); } private long hash(String key, int m) { long h = 0; for (int j = 0; j < m; j++) h = (R * h + key.charAt(j)) % q; return h; } private boolean check(String txt, int i) { for (int j = 0; j < m; j++) if (pat.charAt(j) != txt.charAt(i + j)) return false; return true; } public int search(String txt) { int n = txt.length(); if (n < m) return n; long txtHash = hash(txt, m); if ((patHash == txtHash) && check(txt, 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; int offset = i - m + 1; if ((patHash == txtHash) && check(txt, offset)) return offset; } return n; } private static long longRandomPrime() { BigInteger prime = BigInteger.probablePrime(31, new Random()); return prime.longValue(); } }