您的位置:首页 > 其它

BM字符串匹配算法解析

2017-04-06 15:28 190 查看
BM算法相比较KMP算法较容易理解,两个算法都是字符串的匹配算法,只不过KMP是前缀匹配算法,而BM 是后缀匹配算法,KMP主要依赖
next[]
, 而BM主要依赖
bs[], gs[]
也就是我们常说的坏字符和好后缀。

首先说明一点,BM字符串比较算法是从模式串的末尾向前进行比较的,坏字符是当模式串与目标串比较单个字符,且目标串与模式串不匹配的那个目标串字符。那么此时模式串右边的字符已经完成匹配,这时就需要移动模式串,使得坏字符与模式串右边的字符相应匹配。这样子可以节省很多不需要的比较(好好想想);好后缀是目标串已经和模式串匹配上的那个后缀,同样,我们也可以把模式串向前移动,直到模式串中的某个子串与好后缀匹配,这样也是减少的不必要的比较,那么问题来了,到底是根据坏字符进行移动还是根据好后缀进行移动呢?其实比较一下两种方式移动的距离,选取距离最大的那种移动方式就可以了。

同KMP算法一样,BM算法也需要预处理出
gs[], bs[]
,那么根据上一段的描述,我们大致可以总结出坏字符和好后缀的移动规则:

坏字符:后移位数 = 坏字符的位置 - 模式串中的上一次出现位置(没有出现就是-1)

好后缀:后移位数 = 好后缀的位置 - 搜索词中的上一次出现位置(没有出现也是-1)

”好后缀”的位置以最后一个字符为准。假定”ABCDEF”的”EF”是好后缀,则它的位置以”F”为准,即5(从0开始计算)。

如果”好后缀”在搜索词中只出现一次,则它的上一次出现位置为 -1。比如,”EF”在”ABCDEF”之中只出现一次,则它的上一次出现位置为-1(即未出现)。

如果”好后缀”有多个,则除了最长的那个”好后缀”,其他”好后缀”的上一次出现位置必须在头部。比如,假定”BABCDAB”的”好后缀”是”DAB”、”AB”、”B”,请问这时”好后缀”的上一次出现位置是什么?回答是,此时采用的好后缀是”B”,它的上一次出现位置是头部,即第0位。这个规则也可以这样表达:如果最长的那个”好后缀”只出现一次,则可以把搜索词改写成如下形式进行位置计算”(DA)BABCDAB”,即虚拟加入最前面的”DA”。

下面我们就来分析下代码。

void preGetBs(char s[], int len, int bs[])
{
for (int i = 0; i < ASIZE; i++)
bs[i] = len;
for (int i = 0; i < len - 1; i++)
bs[s[i]] = len - i - 1;
}


注意,这是
bs[]
代表的是字符
c
到模式串末尾的最短距离,首先初始化
bs[] = len
,也就是说使得所有的坏字符移动的距离都是模式串的长度;从头到尾计算距离,且对于同一个字符来说,从头到为计算出来的距离会越来越短,直到最短距离。

void suffix(char s[], int len, int suff[])
{
suff[len - 1] = len;
int g = len - 1, f;
for (int i = len - 2; i >= 0; i--)
if (i > g && suff[len - 1 - (f - i)] < i - g)
suff[i] = suff[len - 1 - (f - i)];
else {
if (i < g)
g = i;
f = i;
while (g >= 0 && s[g] == s[len - 1 - (f - g)])
--g;
suff[i] = f - g;
}
}


注意
suff[]
是这样的一个数组:
suff[i] = s
表示
str[i - s + 1, i]
str[strlen(str) - s, strlen(str) - 1]
。这是为了处理好后缀所做的准备。这个代码中给了很重要的一个优化,就是
if (i > g && suff[len - 1 - (f - i)] < i - g) suff[i] = suff[len - 1 - (f - i)];
看图片



f
最近一次比较开始的地方,
g
是模式串的指针,
i
是当前位置.通过这张图片我们可以发现,
str[g,f]
这段子串是和后缀相匹配的,那么当
i
g
f
之间的时候,我们是不是可以利用已经比较过的结果来计算呢?答案是可以的,
str[g,i]
当然是和后缀匹配的,那么我们只要看下
suff[len - 1 - (f - i)]
的值是不是比
i - g
大,如果大,那么说明
str[0,len - 1 - (f - i)]
与后缀匹配的字符比
str[g, i]
多,也就是说
g
要继续向后移动来判断
g
前面的字符是否有更多的字符与后缀匹配;如果小,很显然,直接可以
suff[i] = suff[len - 1 - (f - i)];
看看图就应该明白了。

void preGetGs(char s[], int len, int gs[])
{
int i, j;

suffix(s, len, suff);

for (i = 0; i < len; i++)
gs[i] = len;
for (j = 0, i = len - 1; i >= 0; i--)
if (suff[i] == i + 1)
for (; j < len - 1 - i; j++)
if (gs[j] == len)
gs[j] = len - 1 -i;
for (i = 0; i <= len - 2; i++)
gs[len - 1 -suff[i]] = len - 1 - i;
}


注意,这个函数有三个部分,其中分别对应与三种情况:好后缀在前面的字符串中没有出现;好后缀在前面的字符串只出现的部分,其一定是出现在字符串开头;好后缀完整出现在前面的字符串。

那么很简单,第一种情况当然需要移动整个模式串的长度,第二种情况判断
suff[i] == i + 1
,如果成立那么,对于
str[i, len - 1 - i]
这之间的位置,
gs[j] = len - 1 -i;
,
if (suff[i] == i + 1)
这条if语句是确保在第二种情况下,
gs[j]
只会被赋值一次,第三种情况,也就是最普通的情况了。而且,这三种情况下对
gs[]
的赋值只会越来越小,这样保证了正确性,因为在移动不同的距离都能使得字符串匹配上后缀的情况下,移动最少的距离会保证解的完整性。

最后贴上完整代码

#include <stdio.h>
#include <string.h>

#include <algorithm>

#define ASIZE 256
#define MAX_LINE 1024 * 128

using namespace std;

char str[MAX_LINE], templet[MAX_LINE];

int Bs[MAX_LINE], Gs[MAX_LINE], suff[MAX_LINE];

void preGetBs(char s[], int len, int bs[]) { for (int i = 0; i < ASIZE; i++) bs[i] = len; for (int i = 0; i < len - 1; i++) bs[s[i]] = len - i - 1; }

void suffix(char s[], int len, int suff[]) { suff[len - 1] = len; int g = len - 1, f; for (int i = len - 2; i >= 0; i--) if (i > g && suff[len - 1 - (f - i)] < i - g) suff[i] = suff[len - 1 - (f - i)]; else { if (i < g) g = i; f = i; while (g >= 0 && s[g] == s[len - 1 - (f - g)]) --g; suff[i] = f - g; } }

void preGetGs(char s[], int len, int gs[]) { int i, j; suffix(s, len, suff); for (i = 0; i < len; i++) gs[i] = len; for (j = 0, i = len - 1; i >= 0; i--) if (suff[i] == i + 1) for (; j < len - 1 - i; j++) if (gs[j] == len) gs[j] = len - 1 -i; for (i = 0; i <= len - 2; i++) gs[len - 1 -suff[i]] = len - 1 - i; }

int main()
{
gets(str);
gets(templet);
/* predealing */
preGetBs(templet, strlen(templet), Bs);
preGetGs(templet, strlen(templet), Gs);

/* matching */
/* BM */
int i, j = 0, m = strlen(templet), n = strlen(str);
while (j + m <= n) {
for (i = m - 1; i >= 0 && templet[i] == str[i + j]; i--);
if (i < 0)
printf("%d\n", j), j += Bs[0];
else
j += max(Gs[i], Bs[str[i + j]] - (m - 1 - i));
}
return 0;
}


参考:

字符串匹配的Boyer-Mo
c77d
ore算法;作者: 阮一峰

算法代码参考;英文
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: