Boyer-Moore字符串查找算法
在用于查找子字符串的算法当中,BM(Boyer-Moore)算法被认为最高效的字符串搜索算法,它由Bob Boyer和J Strother Moore设计于1977年。 一般情况下,比KMP算法快3-5倍。该算法常用于文本编辑器中的搜索匹配功能,比如大家所熟知的GNU grep命令使用的就是该算法,这也是GNU grep比BSD grep快的一个重要原因。
启发式的处理不匹配的字符
如图显示了在文本FINDINAHAYSTACKNEEDLEINA中查找模式NEEDLE的过程。因为是从右向左与模式进行匹配,所以首先会比较模式字符串中的E和文本中的N(位置为5的字符)。因为N也出现在了模式字符串中,所以将模式字符串向右移动5个位置,将文本中的字符N和模式字符串中(最左侧)的N对齐。然后比较模式字符串最右侧的E和文本中的S(位置在第10个字符),匹配失败。但因为S不包含在模式字符串中,所以可以将模式字符串向右移动6个位置。此时模式字符串最右侧的E和文本中位置为16的E相匹配,但我们发现文本的下一个(位置为15的)字符为N,匹配再次失败。于是和第一次一样,将模式字符串再次向右移动4个位置。最后,从位置20处开始从右向左扫描,发现文本中含有与模式匹配的子字符串。这种方法找到匹配位置仅用了4次字符比较(以及6次比较来验证匹配)!
起点
要实现启发式地处理不匹配的字符,我们使用数组right[]记录字母表中的每个字符在模式中出现的最靠右的地方(如果字符在模式中不存在则 表示为-1)。这个值揭示了如果该字符出现在文本中且在查找时造成了一次匹配失败,应该向右跳跃多远。要将right[]数组初始化,首先将所有元素的值设为-1,然后对于0到M-1的j,将right[pat.charAt(j)]设为j,如图所示。
子字符串的查找
我们用一个索引i在文本中从左向右移动,用另一个索引j在模式中从右向左移动。内循环会检查正文和模式字符串在位置i是否一致。如果从M-1到0的所有j,txt.charAt(i+j)都和pat.charAt(j)相等,那么就找到了一个匹配。否则匹配失败,就会遇到以下三种情况:
-
如果造成匹配失败的字符不包含在模式字符串中,将模式字符串向右移动j+1个位置(即将i增加j+1).小于这个偏移量只可能使该字符与模式中的某个字符重叠。事实上,这次移动也会将模式字符串前面一部分已知的字符和模式结尾的一部分已知字符对齐。
-
如果造成匹配失败的字符包含在模式字符串中,那就可以使用right[]数组来将模式字符串和文本对齐,使得该字符和它在模式字符串中出现的最右位置相匹配。和刚才一样,小于这个偏移量只可能使该字符和模式中的与它无法匹配的字符(比它出现的最右位置更靠右的字符)重叠。
-
如果这种方式无法增大i,那就直接将i加1来保证模式字符串至少向右移动了一个位置。
性能
在一般情况下,对于长度为N的文本和长度为M的模式字符串,使用Boyer-Moore的子字符串查找算法通过启发式处理不匹配的字符需要~N/M次字符比较。
完整的Boyer-Moore算法预计算了模式字符串与自身的不匹配情况并为最坏情况提供了线性级别的运行时间保证。
package section5_3;
public class BoyerMoore {
private int[] right;
private String pat;
public BoyerMoore(String pat) {
this.pat = pat;
int M = pat.length();
int R = 256;
right = new int[R];
for (int c = 0;c < R;c++) {
right[c] = -1; //不包含在模式字符串中的字符的值为-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 (pat.charAt(j) != txt.charAt(i+j)) {
skip = j - right[txt.charAt(i+j)];
if (skip < 1) {
skip = 1;
}
break;
}
}
if (skip == 0) return i;
}
return N;
}
public static void main(String[] args) {
String txt = "FINDINAHAYSTACKNEEDLEINA";
String pat = "NEEDLE";
BoyerMoore bm = new BoyerMoore(pat);
System.out.println("text :" + txt);
int offset = bm.search(txt);
System.out.print("pattern :");
for (int i = 0;i < offset;i++) {
System.out.print(" ");
}
System.out.println(pat);
}
}
输出: