BM字符串匹配算法解析
来源:互联网 发布:计算机通信与网络 编辑:程序博客网 时间:2024/05/09 04:16
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 * 128using 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-Moore算法;作者: 阮一峰
- 算法代码参考;英文
- BM字符串匹配算法解析
- BM字符串匹配算法
- BM字符串匹配算法
- 字符串匹配-BM算法
- BM字符串匹配算法
- BM字符串匹配算法
- BM字符串匹配算法
- BM算法 字符串匹配算法
- 字符串匹配(BF,BM,Sunday,KMP算法解析)
- 字符串匹配之BM算法
- 字符串匹配之BM算法
- 字符串匹配 — BM算法
- 字符串匹配之BM算法
- BM字符串匹配算法笔记
- 字符串模式匹配算法---BM算法
- 字符串匹配算法——BM算法
- 字符串模式匹配的BM算法
- BM字符串匹配算法的java实现
- Nginx负载均衡配置
- Android开发之——自定义标题栏titlebar
- 企业App下载无法安装,提示"此时无法安装App,xxx"
- 蚂蚁金服面试相关流程及关注核心技术方向收集
- TCP协议详解及三次握手与四次分手
- BM字符串匹配算法解析
- 【mybatis】mybatis数据源源码剖析(JNDI、POOLED、UNPOOLED)
- 结合npm prepublish完成devDependencies精简dependencies依赖
- phpstorm 常用设置
- 2012蓝桥杯试题第四题
- xUtils使用详解(一)
- Linux NFS服务器的安装与配置
- 创建.a文件
- Ubuntu下修改环境变量的三种方法