1.BM算法是什么?
BM算法是一种非常高效的字符串搜索算法。此算法仅对搜索目标字符串(关键字)进行预处理,而非被搜索的字符串。虽然Boyer-Moore算法的执行时间同样线性依赖于被搜索字符串的大小,但是通常仅为其它算法的一小部分:它不需要对被搜索的字符串中的字符进行逐一比较,而会跳过其中某些部分。通常搜索关键字越长,算法速度越快。
2.BM算法实现
BM算法实际上是在简单的后缀匹配算法的基础上,添加坏字符(__Bad_Char)和好后缀(__Good_Suffix)原则,实现加速移动字符串的速度。
char *BM(char *text, char *pattern) {
if (text == NULL) return NULL;
unsigned int text_len = strlen(text);
unsigned int pat_len = strlen(pattern);
unsigned int shift = pat_len - 1; // 存储text的当前匹配下标
unsigned int i = 0; // 存储从shift向前匹配的长度
while (shift < text_len) {
for (i = 0; i < pat_len; i++)
if (text[shift - i] != pattern[pat_len - 1 - i]) goto NEXT;
return text += shift - (pat_len - 1);
NEXT:
shift++;
}
return NULL;
}
2.坏字符原则(__Bad_Char)
#define ASIZE 256
typedef struct
{
unsigned char*pattern; // 存储适配串
unsigned int patlen; // 存储适配串长度
unsigned int bad_shift[ASIZE];
unsigned int good_shift[0]; //
} ts_bm;
/*
* 初始化坏字符和好后缀
*/
ts_bm *bm_init(const char *pattern, unsigned int len) {
ts_bm *bm = NULL;
unsigned int prefix_tbl_len = len * sizeof(int); // 好后缀列表长度
size_t priv_size = sizeof(*bm) + len + prefix_tbl_len;
bm = (ts_bm *)malloc(priv_size);
memset(bm, 0, priv_size);
bm->patlen = len;
bm->pattern = (uint8_t *)bm->good_shift + prefix_tbl_len;
memcpy(bm->pattern, pattern, len);
compute_prefix_tbl(bm);
return bm;
}
void compute_prefix_tbl(ts_bm *bm) {
// 主串遇到坏字符时,一次性移动的长度
for (unsigned int i = 0; i < ASIZE; i++) bm->bad_shift[i] = bm->patlen;
// 任意字符从右侧开始的下标(重复字符取最右侧的元素)
for (unsigned int i = 0; i < bm->patlen - 1; i++)
bm->bad_shift[bm->pattern[i]] = bm->patlen - 1 - i;
}
BM算法添加坏字符(__Bad_Char)规则
ts_bm *bm = bm_init(pattern, pat_len);
/*
* text[shift-i]匹配失败, k = bad_shift[a]
* ↓ ← i → ↓
* text:・・・・・a・・・・・[shift]
* ↓ ← k → ↓
* patt:・・a・・・・・・・・[0]
* 若需将a对齐,则需将text向左平移(k-i)位
*
*/
shift += bm->bad_shift[text[shift - i]] - i;
3.好后缀原则(__Good_Suffix)
void compute_prefix_tbl(ts_bm *bm) {
// 主串遇到坏字符时,一次性移动的长度
for (unsigned int i = 0; i < ASIZE; i++) bm->bad_shift[i] = bm->patlen;
// 任意字符从右侧开始的下标(重复字符取最右侧的元素)
for (unsigned int i = 0; i < bm->patlen - 1; i++)
bm->bad_shift[bm->pattern[i]] = bm->patlen - 1 - i;
#ifdef __Good_Suffix
int len = bm->patlen;
int *suffix = (int *)malloc(len);
suffix[len - 1] = len;
for (int i = len - 2; i >= 0; i--) {
int shift = 0;
// 满足P[i - shift, i] = P[len - 1 - shift, len - 1]即好后缀
// 为避免过度滑动,从左侧开始寻找
while (shift <= i && bm->pattern[i - shift] == bm->pattern[len - 1 - shift])
shift++;
// i为P[i] = P[len - 1]时的起始下标
// suffix[i] = 好后缀/最长前缀长度
suffix[i] = shift;
}
// good_shift[i]存储pattern[i]失配时向右滑动的长度
for (int i = 0; i < len; i++) bm->good_shift[i] = len;
// 从后向前,保证最长前缀唯一
for (int i = len - 1; i >= 0; i--) {
if (suffix[i] == i + 1) // 当suffix是P前缀时
{
// P中存在一个最长前缀,等于好后缀的后缀
// 此时,P[0,i]是P[j, len-1]的最长前缀(k < len - 1 - i)
for (int j = 0; j < len - 1 - i; j++) {
// 保证短前缀不覆盖长前缀
if (bm->good_shift[j] == len) bm->good_shift[j] = len - 1 - i;
}
}
}
// 好后缀
for (int i = 0; i < len - 1; i++) {
// 对长度 k = suffix[i]的好后缀
// 满足P[len - 1 - k, len - 1] = P[i - k, i]
// 即最大滑动长度 = len - 1 - i
bm->good_shift[len - 1 - suffix[i]] = len - 1 - i;
}
if (suffix != NULL) free(suffix);
#endif
}
BM算法添加坏字符(__Bad_Char)和好后缀(__Good_Suffix)规则
shift += MAX(shift += MAX(bm->good_shift[pat_len - 1 - i],, bm->bad_shift[text[shift - i]] - i);
4.好后缀原则(__Kernel)
以下代码来自Linux源码 lib/ts_bm.c
int subpattern(uint8_t *pattern, int i, int j, int g) {
// x始终指向P[len-1]
int x = i + g - 1, y = j + g - 1, ret = 0;
while (pattern[x--] ==
pattern[y--]) // 将pattern[y]和pattern[n-1]从后向前比较
{
if (y < 0) // y向前回溯j个元素回到pattern[0]
{
ret = 1; // 指向最长前缀
break;
}
if (--g == 0) // y向前回溯j个元素
{
ret = pattern[i - 1] != pattern[j - 1]; // 指向好后缀
break;
}
}
return ret;
}
void compute_prefix_tbl(ts_bm *bm) {
// 主串遇到坏字符时,一次性移动的长度
for (unsigned int i = 0; i < ASIZE; i++) bm->bad_shift[i] = bm->patlen;
// 任意字符从右侧开始的下标(重复字符取最右侧的元素)
for (unsigned int i = 0; i < bm->patlen - 1; i++)
bm->bad_shift[bm->pattern[i]] = bm->patlen - 1 - i;
#ifdef __Good_Suffix
#ifdef __Kernel
unsigned int i, j, g;
bm->good_shift[0] = 1;
for (i = 1; i < bm->patlen; i++) bm->good_shift[i] = bm->patlen;
// i从P[len - 1]开始,表示后缀
// j从P[len - 2]开始,表示匹配好后缀或者最大前缀的串
// g = [0, len-1], 表示i和j匹配成功的长度
for (i = bm->patlen - 1, g = 1; i > 0; g++, i--) {
for (j = i - 1; j >= 1 - g; j--) {
// 将P[i-1,i+g-1]与P[max(j-1,0), j+g-1]进行匹配
if (subpattern(bm->pattern, i, j, g)) {
bm->good_shift[g] = bm->patlen - j - g;
break;
}
}
}
#endif
#endif
}
BM算法添加坏字符(__Bad_Char)和好后缀(__Good_Suffix)规则
shift += MAX(shift += MAX(bm->good_shift[i],, bm->bad_shift[text[shift - i]] - i);