BM算法Java代码实现

最近整理了一下BM算法实现,浏览了很多帖子,发现一些帖子说的很多,但是给出的场景相对局限,要么就是model串是存在前后缀一致的,要么就是查找的结果都在最后。

本文考虑到以上两种情况:

1.好后缀可以匹配的字符在并不是前缀,是前缀中的一部分

2.如果主串已经匹配到model串了,但没匹配完,主串还有,那后面如何操作?

然后给出具体实现代码。

BM算法逻辑

用图简单说一下BM查找逻辑:

 

 

 代码实现

/**
 * BM算法
 * @author SuMalago
 *
 */
public class BMCalculator {

	public static void main(String[] args) {
		String partterns = "ABDCABDA";
		String text = "ABDCADAAABDCABDADDABDCABDA";
		
		BMSearch(text.toCharArray(), partterns.toCharArray());
	}
	
    /**
     * BM查找
     * @param mainStr 长串
     * @param modelStr 短串
     */
    private static void BMSearch(char[] mainStr, char[] modelStr){
    	//modelStr在mainStr的偏移位置
    	int start = 0;
    	int goodIndex = 0;
    	int badIndex = 0;
    	int[] prefixArray = initGoodSuffix(modelStr);
    	
    	//-----以下是处理匹配完成后的后续操作,最后看这里,先看其他的----------start
    	int commonEleMaxIndex = modelStr.length;//默认取该串长度,即这个串里面所有字符没有重复的
    	for(int i=prefixArray.length-1;i>0;i--){
    		if(prefixArray[i] > -1){
    			//prefixArray数组的下标是后缀长度,值是最后一次相同后缀字符所在位置、
    			//下标越小,例如1,其实际在modelStr中越靠后,即元素位置=modelStr.length-1
    			commonEleMaxIndex = modelStr.length-i-prefixArray[i];
    		}
    	}
    	//----------------------end---------------------------------
    	
    	while(modelStr.length + start <= mainStr.length){//确保不会超
    		//取出好后缀的最大偏移量
    		goodIndex = goodSuffix(mainStr,modelStr,start,prefixArray);
    		if(goodIndex == 0){
    			System.out.println(" BM is find : the index -> "+start);
    			//找到之后,但mainStr没循环完,直接查找最大后缀是匹配元素,然后使用好后缀的匹配规则后移
    			//找到此modelStr存在的最大的后缀,然后后移,比start+1要省时间
    			
    			goodIndex = commonEleMaxIndex;
    		}
    		//取出坏字符的最大偏移量
    		badIndex = badChar(mainStr,modelStr,start);
    		//好后缀和坏字符,哪个大用哪个
    		start = start + (goodIndex >= badIndex ? goodIndex : badIndex);
    	}
    }
	
	/**
	 * 给modelStr使用的全量数组<br/>
	 * 这里是先把modelStr字符串中所有字符存在一个数组里
	 * 后面重复的位置的字符会替代之前的
	 * 因为:在查找时是倒着查找,所以会先取后面的字符位置
	 * 
	 * @param modelStr 假设这个字符串都是由大写字母组成 ,自己实现也可以用ASCII码
	 * @return
	 */
	private static int[] initModelChr(char[] modelStr){
		//一共26个大写字母,初始值都设为-1
		//假设长字符串text中的某个字符在匹配modelStr时,该位置下的字符在modelStr中没找到,即返回-1
		int[] existModelArray = new int[26];
		for (int i = 0 ; i < 26 ; i ++) {
			existModelArray[i] = -1;
        }
		
		//循环modelStr内容,将其内容里的大写字母对应到existModelArray中,后面重复的覆盖前面的
		for (int i = 0 ; i < modelStr.length ; i++) {
			existModelArray[modelStr[i] - 'A'] = i;//假设modelStr中第一个字符是B,则对应的位置就是 existModelArray[1]
        }
		return existModelArray;
	}
	
	/**
	 * 坏字符匹配模式下,需要移动的最大位置
     * @param mainStr 主串
     * @param modelStr 模式串
     * @param start 模式串在主串中的起始位置
     * @return 模式串可滑动距离,如果为0则匹配上
     */
    private static int badChar(char[] mainStr, char[] modelStr, int start) {
    	
    	int[] existModelArray = initModelChr(modelStr);
    	//主串的位置下标
    	int mainIndex = 0;
    	//主串中有的,但是modelStr中没有匹配到的 【坏字符】
    	char badChar = '\0';
    	//需要返回的坏字符的移动位置 默认0:没有坏字符,全匹配了
    	int returnBadCharMoveIndex = 0;
    	//倒着匹配
    	for(int i=modelStr.length-1; i >= 0 ;i--){
    		//主串实际下标的位置 = modelStr的起始位置+model倒序匹配的位置
    		mainIndex = start + i;
    		//检查没匹配上,mainStr[mainIndex]就是坏字符
    		if(mainStr[mainIndex] != modelStr[i]){
    			badChar = mainStr[mainIndex];
    			//检查modelStr中是否有这个字符,从而找出坏字符的匹配位置
    			if(existModelArray[badChar - 'A'] == -1){//没有  
    				returnBadCharMoveIndex = i;
    			}else if(existModelArray[badChar - 'A'] > -1){//modelStr存在这个字符 
    				returnBadCharMoveIndex = i - existModelArray[badChar - 'A'];
    			}
    			//检查到坏字符就跳出去
    			break;
    		}
    	}
    	return returnBadCharMoveIndex;
    }
    
    //好前缀的逻辑-------------------------------------
    /**
     * 寻找modelStr字符串中的 后缀的前面的所有能匹配的组合
     * 比如ABCAB,后缀是B时查找相同元素B,即下标是1;后缀是AB时查找AB,下标是0
     * @param modelStr
     * @return int[] 下标是后缀长度,其值是与后缀长度一致的字符在modelStr最靠后的位置
     */
    private static int[] initGoodSuffix(char[] modelStr) {
    	StringBuffer prefix = new StringBuffer();
    	StringBuffer suffix = new StringBuffer();
    	//随着每次的循环,后缀一直在递增 A.BA.DBA.CDBA.... 但不会过半,因为过半前面的就无法匹配后缀了
    	int tempSuffixCharLen = 0;
    	
    	int[] prefixArray = new int[modelStr.length/2+1];
    	//这个数组下标是从1开始,0默认赋值-1
    	prefixArray[0] = -1;
    	int tempPrefixValue = -1;//默认没有 -1
    	
    	for(int i=0;i<modelStr.length/2;i++){
    		//倒序 结果值往前插入 此时得到最新后缀,但此后缀之前只要存在的字符串和本后缀内容一致,就可以做映射(这里肯定包含前缀,但不仅限于前缀)
    		suffix.insert(0,modelStr[modelStr.length - 1 -i]); 
    		tempSuffixCharLen = suffix.length();
    		/*
    		 * 过滤本后缀之前的所有相同内容的字符串,要求:
    		 * ①过滤的字符串长度要和suffix的长度一致,比如都是AB
    		 * ②如果过滤过程中存在重复,则用后面的覆盖掉前面的,这是因为后面移动的时候如果移动前面的,会导致部分匹配内容漏掉
    		 */
    		for(int j=0;j<modelStr.length - 1 -i ;j++){
    			//每次进循环清空
    			prefix = new StringBuffer();
    			//这里面的循环是用来将后缀 前面的字符拼起来,使其长度和后缀suffix长度一致,因为你不知道后缀长度有多少,所以就得用循环
    			for(int x=j;x<modelStr.length - 1 -i ;x++){
    				prefix.append(modelStr[x]);
    				if(prefix.length() == tempSuffixCharLen){
    					//长度一致,内容一致
    					if((prefix.toString().equals(suffix.toString()))){
    						//能匹配上,代表第j次循环取tempSuffixCharLen个字符和后缀能匹配上
    						tempPrefixValue = j;
    					}
    					//注意这里,一旦长度相同就跳出去,后面的不循环匹配了,因为外循环有j++循环
    					break;
    				}
    			}
    		}
    		//本次tempSuffixCharLen长度的后缀匹配结束,下次循环tempSuffixCharLen+1
    		prefixArray[tempSuffixCharLen] = tempPrefixValue;
    	}
    	return prefixArray; 
    }
    
    /**
     * 
     * @param mainStr 主串
     * @param modelStr 模式串
     * @param start 模式串在主串中的起始位置
     * @return 模式串可滑动距离,如果为0则匹配上
     */
    private static int goodSuffix(char[] mainStr, char[] modelStr, int start,int[] prefixArray){
    	//第一步是这个,但是外面调用了,传进来里面不用调了
//    	int[] prefixArray = initGoodSuffix(modelStr);
    	
    	//主串的位置下标
    	int mainIndex = 0;
    	//需要返回的坏字符的移动位置 默认0:没有坏字符,全匹配了
    	int returnGoodSuffixMoveIndex = 0;
    	//后缀内容
    	StringBuffer suffix = new StringBuffer();
    	//临时后缀长度,由长到短依次递减
    	int tempSuffixCharLen = 0;
    	
    	mainFor:
    	//倒着匹配
    	for(int i=modelStr.length-1; i >= 0 ;i--){
    		//主串实际下标的位置 = modelStr的起始位置+model倒序匹配的位置
    		mainIndex = start + i;
    		//当匹配到坏字符时,坏字符后面的 都是好后缀
    		if(mainStr[mainIndex] != modelStr[i]){
    			//i位置上对应的已经是坏字符了,所以i后面才是 好后缀 即i+1
    			//如果最后一个就是坏字符,没有好后缀
    			if(i==modelStr.length-1){
    				returnGoodSuffixMoveIndex = -1;
    				break;
    			}else{
    				//好后缀是 从i+1 -> modelStr.length-1,由1个到多个的组合情况:例如字符串ABADB,其后缀 BADB ADB DB B
    				while(suffix.length() > 0){
    					//判断最长的后缀看能不能匹配到,然后依次后缀递减
        				tempSuffixCharLen = suffix.length();
        				if(prefixArray[tempSuffixCharLen] > -1){//存在一致的元素在前面
        					//要返回的长度=好后缀的下标-最后出现此后缀字符的下标
    						returnGoodSuffixMoveIndex = (i+1) - prefixArray[tempSuffixCharLen];
    						break mainFor;
        				}else{
        					//如果没有,则suffix去掉前面的一位,再循环检查
        					suffix = suffix.delete(0, 1);
        				}
    				}
    			}
    			
    		}else{
    			//能匹配上,取后缀
    			suffix.insert(0,modelStr[i]); 
    		}
    	}
    	return returnGoodSuffixMoveIndex;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值