字符串匹配——BM算法

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);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值