Boyer–Moore–Horspool文本匹配算法(BM算法的简化版)

英语里有句习语叫"find a needle in a haystack",译成中文叫"大海捞针"(原意是在一堆干草中寻找一根针)。计算机中的文本匹配(string matching)就是要解决怎样在一段很长的文本中找到符合要求的一个子串,该子串通常叫模式串(pattern),也就是对应我们要找的“针”。常用 的文本精确匹配(exact  string matching)算法有蛮力法(brute-force),Boyer-Moore算法和KMP算法。后两种算法都是用空间换 时间的经典案例,不像蛮力法,文本的位置指针i不需要回退到已经匹配过的字符位置(BM算法的每次匹配虽然从右向左移动指针,但从来不会将i指针回退到已 经匹配过的字符位置)。在最坏情况下的时间复杂度是与文本长度成线性关系的。特别需要说明的是,BM算法和本文中介绍的它的简化版本Horspool算 法,在最佳情况下的时间复杂度是O(n/m),因此对模式串比较长的情况(比如长度大于5)应用该算法比较有效。

本文介绍Boyer-Moore算法的一个简化版本,叫做Boyer–Moore–Horspool算法或直接叫做Horspool算法。

设文本T长度为n,模式串P长度为m。T的当前位置指针为i(0<=i<n),P的当前位置指针为j(0<=j<m)。

算法:
基本思想是模式串窗口沿着文本向右滑动,但是每次滑动的距离不能太大,否则会冒着遗漏可能成功匹配的字符串的风险。在模式串窗口滑动完成后,开始重新设置 i和j指针,并且让i和j分别从右到左挨个字符匹配。匹配到某个字符位置时发现T[i]和P[j]不相同了(即发现了mismatch,字符T[i]也叫 做bad  character),就要分以下三种情况讨论:

1)如果T[i]不在模式串中存在,需要将模式串窗口沿着文本向右滑动j+1个字符位置。这是因为j前面的任何字符P[0..j-1]都与T[i]不匹配,可以直接将P[0]跟i的下一个字符T[i+1]对齐用作下一次的匹配。
例如:


此时j=3,T[i]=T,P[j]=D,但是字符T[i]=T在模式串中没有出现。直接将P[0]=N跟i的下一个字符T [i+1]=L对齐用作下一次的匹配。所以下一次滑动窗口的位置如上所示。

2)如果T[i]在模式串中存在,分两种情况讨论:

case 2a) 设P中最右边出现的字符T[i]所处的位置为q,但是q>j,这时如果还要让T[i]跟P[q]对齐的话,势必要让模式串窗口沿着文本向左移动,显然这是做无用功。所以正确而且最保守的做法是将模式串窗口沿着文本向右移动1个字符位置。
例如:
 
此时j=4,T[i]=E,P[j]=L,这里字符T[i]=E在模式串中最右边位置q=5(注意必须是最右边位置,左边的两个E不算),因为q=5>j=4,所以最保守的情况是将P只向右滑动一个字符位置,如上图所示。

case 2b)如果q<j (显然q不可能等于j了,因为T[i] != P[j]),这是就需要将T[i]跟P[q]对齐,此时能保证模式串窗口会向右滑动。模式窗口向右滑动的字符个数为j-q。
例如:

此时j=4,T[i]=D,P[j]=L,这里字符T[i]=D在模式串中最右边位置q=3,因为q=3<=j=4,所以将模式窗 口向右滑动j-q=1个字符以保持T[i]=D和j左边的第一个字符D对齐,如上图所示。

注意在下一次匹配时,需要调整j到模式串的末尾字符位置,并且i和j对齐。可用公式得出:
  i <- i+m-min(j,1+q)
  j <- m -1
二者顺序不能颠倒,因为计算i时需要j的原来值。

根据上面的讨论前提是需要知道q的值,即解决这样一个问题:某个字符c(即bad character)在模式串P中最右边出现的位置是多少?考虑到文本中这样的c可能为任意字符,需要预先计算一张表(叫做bad- character shift表, 表的索引值范围即字符集的所有字符值。具体实现时,如果c在模式串中不出现, 该表对应预设值-1。参考下面的实现。


算法复杂度:Horspool算法在平均情况下的时间复杂度是O(n),但是在最坏情况下的复杂度跟蛮力法在同一个数量级,即O(mn),非简化版本的 BM算法在最坏情况下的复杂度是线性O(n)。本博客另外一篇文章将介绍原始的BM算法(BM算法是1977年发明的,而Horspool算法是1980 提出来的)如何在最坏的情况下复杂度跟KMP算法同级即O(n), 。


最坏情况:
例如文本中只含一个字母:T=AAAAAAAAA...AA, 模式串P=BAAA。应用在本算法中的case 2a每次只将模式串沿文本向右滑动一个字符。这跟蛮力法的操作次数一样多。

最佳情况:
文本T=After a long text, here's a needle ZZZZZ
模式串P=ZZZZZ
匹配的过程如下(注意每次i和j指针跳跃距离都是模式串长度m):


                    
实现:

import java.util.Arrays;
/**
 * 
 * @author ljs
 * 2011-06-19
 * 
 * 版权说明:可以复制,但须标明出处 
 * 
 * Assuming we use ASCII charset
 *
 */
public class Horspool {
	private static final int CHARSET_SIZE = 256;
	//prepare the shift table: find the rightmost position 
	//in the pattern
	private int[] makeShiftTable(String pattern){
		int[] shiftTable = new int[CHARSET_SIZE];
		//init to -1
		for(int i=0;i<CHARSET_SIZE;i++)
			shiftTable[i]=-1;
		//set the pattern chars
		for(int i=0;i<pattern.length();i++){
			char c = pattern.charAt(i);	
			//OK: the rightmost position i may overwrite the left positions
			shiftTable[c] = i;			
		}		
		return shiftTable;
	}
	//return the first matched substring's position;
	//return -1 if no match
	public int match(String text,String pattern){
		int n = text.length();
		int m = pattern.length();
		if(m>n) return -1;
		
		int[] shiftTable = makeShiftTable(pattern);
		
		/****BEGIN TEST: the following code snippet can be commented out****/
		System.out.format("%s%n",text);
		System.out.format("%s%n",pattern);
		/****END TEST: the above code snippet can be commented out****/
		
		int i = m -1;
		int j = m -1;
		do{
			int c = text.charAt(i);
			if(c == pattern.charAt(j)){
				if(j==0){				
					//find a match
					return i;
				}else{
					//BM algorithm: move from right to left
					i--;
					j--;
				}			
			}else{			
				/****BEGIN TEST: the following code snippet can be commented out****/
				int q = shiftTable[c];
				int dotsCount = 0;
				if(q == -1){//case 1
					dotsCount = i+1;
				}else if(q>j){ //case 2a
					dotsCount = i-j+1;	
				}else{ //case 2b
					dotsCount = i-q;
				}
				byte dot[] = new byte[dotsCount];
			    Arrays.fill(dot, (byte)'.');						
				System.out.format("%s%s%n",new String(dot),pattern);		
				/****END TEST: the above code snippet can be commented out****/
				
				//determine the i and j for next match attempt				
				int p = shiftTable[c] + 1;		
				if(j<=p){
					i += m - j;
				}else{
					i += m - p;
				}			
				j = m - 1;
			}
		}while(i<=n-1);
		
		//no match
		return -1;
	}
	public static void findMatch(Horspool solver,String text,String pattern){		
		int index = solver.match(text, pattern);
		if(index>=0){
			System.out.format("Found at position %d%n",index);
		}else{
			System.out.format("No match%n");
		}
	}
	public static void main(String[] args) {
		Horspool solver = new Horspool();
		
		String text = "A SLOW TURTLE";
		String pattern = "NEEDLE";		
		Horspool.findMatch(solver,text,pattern);
		
		System.out.format("**************%n");
		
		text = "ABCELE";
		pattern = "NABDLE";		
		Horspool.findMatch(solver,text,pattern);
		
		System.out.format("**************%n");
		
		text = "After a long text, here's a needle ZZZZZ";
		pattern = "ZZZZZ";		
		Horspool.findMatch(solver,text,pattern);
		
		System.out.format("**************%n");
		
		text = "The quick brown fox jumps over the lazy dog.";
		pattern = "lazy";		
		Horspool.findMatch(solver,text,pattern);
		
		System.out.format("**************%n");
		text = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna...";
		pattern = "tempor";		
		Horspool.findMatch(solver,text,pattern);
	}
}

测试结果:

A SLOW TURTLE
NEEDLE
......NEEDLE
.......NEEDLE
...........NEEDLE
No match
**************
ABCELE
NABDLE
.NABDLE
No match
**************
After a long text, here's a needle ZZZZZ
ZZZZZ
.....ZZZZZ
..........ZZZZZ
...............ZZZZZ
....................ZZZZZ
.........................ZZZZZ
..............................ZZZZZ
...................................ZZZZZ
Found at position 35
**************
The quick brown fox jumps over the lazy dog.
lazy
....lazy
........lazy
............lazy
................lazy
....................lazy
........................lazy
............................lazy
................................lazy
...................................lazy
Found at position 35
**************
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna...
tempor
......tempor
............tempor
..................tempor
.....................tempor
...........................tempor
...............................tempor
....................................tempor
..........................................tempor
................................................tempor
......................................................tempor
..........................................................tempor
...........................................................tempor
.................................................................tempor
..................................................................tempor
........................................................................tempor
.........................................................................tempor
Found at position 73
posted @ 2011-06-19 14:54  ljsspace  阅读(1466)  评论(0编辑  收藏  举报