BM字符串匹配算法

 BM算法可能是相对来说,算法效率最高的一种,一般是KMP算法的3~5倍。

    BM算法是从后缀比较法开始的。就是给出的算法(1)代码。然而,普通的后缀比较法一般移动只有1。而BM算法其实是对后缀蛮力匹配算法的改进。为了实现更快移动模式

串,BM算法定义了两个规则,好后缀规则和坏字符规则。用好后缀和坏字符可以大大加快模式串的移动距离,不是简单的 j,而是j =max (shift(好后缀), shift(坏字符))。

   

具体算法,如下图(网上百度别人博客中的图):

      1、坏字符算法(情况分为两种):

       

  • 坏字符没出现在模式串中,这时可以把模式串移动到坏字符的下一个字符,继续比较,如下图:

  • 坏字符出现在模式串中,这时可以把模式串第一个出现的坏字符和母串的坏字符对齐,当然,这样可能造成模式串倒退移动,如下图:

 

为了用代码来描述上述的两种情况,设计一个数组bmBc['k'],表示坏字符‘k’在模式串中出现的位置距离模式串末尾的最大长度,那么当遇到坏字符的时候,模式串可以移动距离为: shift(坏字符) = bmBc[T[i]]-(m-1-i)。如下图:

 

    2、好后缀算法(分为三种情况)

      

        

  • 模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐即可,如果超过一个子串匹配上好后缀,则选择最靠左边的子串对齐。

  • 模式串中没有子串匹配上后后缀,此时需要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐即可。

  • 模式串中没有子串匹配上后后缀,并且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。

为了实现好后缀规则,需要定义一个数组suffix[],其中suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度,如下图所示,用公式可以描述:满足P[i-s, i] == P[m-s, m]的最大长度s。

   

#include <stdio.h>

//计算字符串长度
int str_len(const char* str)
{
	int len=0;
	while(*str++)
		len++;
	return len;
}

//获取两个整数最大值
int MAX(int a, int b)
{
	return a>b?a:b;
}

//坏字符算法,主要用来在移动时找到最大位移S,坏字符即移动最大距离
void BM_BadChar(int SubLen, int CharByte[256], const char *SubString)
{
	for(int i=0; i<SubLen; ++i)      //SubLen-1,不考虑最后一个数
		CharByte[SubString[i]] = SubLen-i;      //如果i最后一个为SubLen-1,则最后一个距离根据计算,需要-1
}

int BM_Match(const char *srcStr, const char *subStr) //Source为主串,SubString为子串
{
	int srcLen = str_len(srcStr);
	int subLen = str_len(subStr);
	int m=subLen;
	//坏字符数组 (其中ByteChar数组的下标为字符,表示此字符出现最后一次离子串尾最近的距离)
	int byteChar[256];     //256是因为下标为字符,字符为ASCII码,8位,总共有256种组合
	for(int i=0;i<256;++i)
		byteChar[i] = subLen;
	
	//好后缀数组(其中Suff数组n内数字主要表示好后缀的最右边下标与最左边之间的差值)
	//i.如果在P中位置t处已匹配部分P'在P中的某位置t'也出现,且位置t'的前一个字符与位置t的前一个字符不相同,则将P右移使t'对应t方才的所在的位置。
	//ii.如果在P中任何位置已匹配部分P'都没有再出现,则找到与P'的后缀P''相同的P的最长前缀x,向右移动P,使x对应方才P''后缀所在的位置。
	
	int *suff = new int[subLen];
	suff[subLen-1] = subLen; //Suff数组主要记录以数组内标号为子串中的距离
    int *suff2 = new int[subLen]; //Suff2数组主要记录下标为以i为首字母的字符后缀离最近的匹配字符串的距离
    for(int i=0; i<subLen; i++)
    suff2[i] = 0;         //初始化为0
	
	for(int i=subLen-2; i>=0; i--)
	{
		int k=i;
		while(k>=0 && subStr[k] == subStr[subLen-1-i+k])   //下标SubLen+k-1-i与k相对应(从后面两个数进行比较,从而确定好后缀)
		{
			k--;
		}
		suff[i] = i-k;   //其中i-k为最右边下标,数组中的下标为i,i代表子串中与最好后缀匹配的位置
		if(suff[i] != 0)
			suff2[m-suff[i]] = subLen-1-i;  //确定偏移量
	}
	
	int *bmGs = new int[subLen];  //bmGs数组主要记录当在i处(i处为坏字符),则需要移动的距离
	for(int i=0;i<subLen-1;++i)
		bmGs[i] = subLen;
	
	bmGs[m-1] = 0;
	for(int i=subLen-2;i >= 0;i--) 
	{
		if(suff[m-1-suff2[i+1]] !=0 && suff[m-1-suff2[i+1]]!=11)
			bmGs[i] = suff2[i+1];   //移动距离S,当与好后缀匹配时,移动距离
		else
		{
			for(int j=i+2;j<m;j++)
				if(suff[m-1-suff2[j]] == (m-j)) //判断与前缀是否匹配
				{
					bmGs[i] = suff2[j];   //测算出偏移距离
					break;
				}
		}
	}
	//开始BM算法
	//基于后缀回溯比较法
	int pSubLen  ,pSourLen =  subLen;
	if(subLen==0)  return -1;
	while(pSourLen<=srcLen)   //主串是否到了尽头
	{
		pSubLen = subLen; //初始化
		while(subStr[--pSubLen]==srcStr[--pSourLen])  //进行匹配比较
		{
			if(pSourLen < 0)  return -1;   //如果pSour,以子串长度为一组的主串扫描结束
			if(pSubLen == 0) return  pSourLen;    //为0,匹配成功
		}
		BM_BadChar(pSubLen, byteChar, subStr);    //计算字符偏移量
		int charLen = byteChar[srcStr[pSourLen]];     //进行坏字符算法计算CharLen;
		int SuffLength = bmGs[pSubLen] ;      //进行最好后缀法计算出来的偏移长度
		int maxLen = MAX(charLen, SuffLength);    //最后后缀算法,分情况讨论
		pSourLen += (subLen-pSubLen);
		pSourLen += maxLen>1 ? maxLen:1 ;     //进行偏移,pSour值进行恢复与回溯,SubArry - pSub为以前减去的值补回    
	}

	delete[] suff;
	delete[] suff2;
	delete[] bmGs;
	return -1;
}

char* strreplace(char* str, const char* sub, const char* rep)
{
	int repIndex=0;

	int subLen=str_len(sub);//计算子串长度
	while(true)
	{
		//寻找源字符串中待被替换子串的位置,这里采用效率比KMP模式匹配算法还高的BM算法
		repIndex=BM_Match(str, sub);
		//如果子串匹配不上,那么跳出循环,结束处理
		if(repIndex==-1)
			break;
		//进行一次子串替换操作
		for(int p=0; p<subLen; p++)
			*(str+repIndex+p)=*(rep+p);
	}
	return str;
}

int main(int argc, char** argv)
{
	char str[]="CDTYUASSBCDfBCFGKKBCDbvsdCDTYU";
	char sub[]="CDTYU";
	char rep[]="GGGGG";
	
	printf("%s\n", str);
	char *result=strreplace(str, sub, rep);
	printf("%s\n", result);

	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值