// 全局变量 SIZE privatestaticfinal
int SIZE = 256; // b=模式串数组,m是模式串数组长度,sl是哈希表,默认-1 privatevoidgenerateBC(char[] b, int m, int[] sl){ for (int i = 0; i sl[i] = -1; // 初始化sl } for (int i = 0; i int ascii = (int)b[i]; sl[ascii] = i; } }
接下来先不考虑好后缀规则跟坏字符的负数情况,先大致写出
BM
算法代码。
坏字符BM
publicintbm(char[] a, int n, char[] b, int m){ // 记录模式串中每个字符最后出现的位置 int[] sl = newint[SIZE]; // 构建坏字符哈希表 generateBC(b, m, sl); // i表示主串与模式串对齐的第一个字符 int i = 0; while (i <= n - m) { int j; for (j = m - 1; j >= 0; j--) { if (a[i+j] != b[j]) break; } if (j 0) { // 匹配成功,返回主串与模式串第一个匹配的字符的位置 return i; } //将模式串往后滑动j-bc[(int)a[i+j]]位 i = i + (j - sl[(int)a[i+j]]); } return-1; }
suffix[k] = -1,等于时说明好后缀没匹配上,那就看下子串的匹配情况,好后缀的后缀子串长度是 b[r, m-1],其中 r = [j+2,m-1],后缀子串长度 k=m-r,如果 prefix[k] = true,说明长度为 k 的后缀子串有可匹配的前缀子串,这样我们可以把模式串后移 r 位。
如果都没匹配上,那就直接将模式串后移m位。
// a跟n 分别表示主串跟主串长度。 // b跟m 分别表示模式串跟模式串长度。 publicintbm(char[] a, int n, char[] b, int m){ // 用来记录模式串中每个字符最后出现的位置 int[] sl = newint[SIZE]; // 构建坏字符哈希表 generateBC(b, m, sl); int[] suffix = newint[m]; boolean[] prefix = new boolean[m]; // 构建 suffix 跟 prefix 数组 generateGS(b, m, suffix, prefix); int i = 0; // j表示主串与模式串匹配的第一个字符 while (i <= n - m) { int j; // 模式串从后往前匹配 for (j = m - 1; j >= 0; --j) { // 坏字符对应模式串中的下标是j if (a[i+j] != b[j]) break; } if (j 0) { // 匹配OK ,返回主串与模式串第一个匹配的字符的位置 return i; } // 坏字符计算所得需移动长度 int x = j - sl[(int)a[i+j]]; int y = 0; // j if (j -1) { y = moveByGS(j, m, suffix, prefix); } i = i + Math.max(x, y); } return-1; }
// j = 坏字符对应的模式串中的字符下标 // m = 模式串长度 privateintmoveByGS(int j, int m, int[] suffix, boolean[] prefix){ // 好后缀长度 int k = m - 1 - j; if (suffix[k] != -1) { // 好后缀可以匹配上,返回需移动长度 return j - suffix[k] + 1; } // 有匹配到好后缀子串的模式串前缀子串 for (int r = j+2; r <= m-1; ++r) { if (prefix[m-r] == true) { return r; } } // 没找到直接 移动最大值 return m; }
3.5 复杂度分析
整个BM算法用到了额外的 sl、suffix、prefix三个数组,其中sl数组大小跟字符集大小有关,suffix 数组和 prefix 数组的大小跟模式串长度 m 有关。如果处理字符集很大的字符串匹配问题,bc 数组对内存的消耗就会比较多。
因为好后缀和坏字符规则是独立的,如果我们运行的环境对内存要求苛刻,可以只使用好后缀规则,不使用坏字符规则,这样就可以避免 bc 数组过多的内存消耗。不过,单纯使用好后缀规则的 BM 算法效率就会下降一些了。
思路是将
主串中好前缀的后缀子串和模式串中好前缀的前缀子串进行对比
,获取模式串中最大可以匹配的前缀子串。一般把好前缀的所有后缀子串中,最长的可匹配前缀子串的那个后缀子串,叫作
最长可匹配后缀子串
。对应的前缀子串,叫作
最长可匹配前缀子串
。假如现在最长可匹配后缀子串 =
u
,最长可匹配前缀子串 =
v
,获得
u
跟
v
的长度为
k
,此时在主串中坏字符位置为
i
,模式串中为
j
,接下来将模式串后移
j-k
位,然后将待比较的模式串位置
j = j-k
进行比较。