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;
}