BM算法简单理解
什么是BM算法
BM算法算是RK(模式串与主串按位比较,主串按位后移)算法的变种,提高了移动的范围。达到优化的目的
BM算法的两大核心:坏字符&好后缀的处理。
坏字符
从***高->低***按位比较时的第一个不匹配的字符。
移动位数计算:mi = s i - xi
解析
- mi:主串比较向后移动位数
- si:坏字符对应的模式串下标
- xi:坏字符首次比对成功的模式串下标(初始值-1)
场景一:无一匹配

场景二:存在匹配

好后缀
说明模式串后缀存在部分匹配,那么针对这些部分匹配的内容需要进行优化滑动,如果匹配串再模式串中没有匹配的就可以整体跳过。
解析
一、后缀在模式串没有匹配的,那么可以直接移动到后缀之后

二、整个后缀再模式串中存在匹配,那么可以后缀对齐

三、部分后缀与模式串前缀部分匹配则需要前缀部分对齐了

完整代码
public class BMStringOther {
private static final int SIZE = 256; // 全局变量或成员变量
private void generateBC(char[] b, int m, int[] bc) {
for (int i = 0; i < SIZE; ++i) {
bc[i] = -1; // 初始化 bc
}
for (int i = 0; i < m; ++i) {
int ascii = (int) b[i]; // 计算 b[i] 的 ASCII 值
bc[ascii] = i;
}
}
public int bm(char[] a, int n, char[] b, int m) {
int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
generateBC(b, m, bc); // 构建坏字符哈希表
int i = 0; // i 表示主串与模式串对齐的第一个字符
while (i <= n - m) {
int j;
for (j = m - 1; j >= 0; --j) { // 模式串从后往前匹配
if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是 j
}
if (j < 0) {
return i; // 匹配成功,返回主串与模式串第一个匹配的字符的位置
}
// 这里等同于将模式串往后滑动 j-bc[(int)a[i+j]] 位
// 想想为什么这里可以?
// 首先这里从BC中拿到的就是匹配模式串中最近的下标。【所有模式串hash化是可以从后->前覆盖的】
i = i + (j - bc[(int) a[i + j]]);
}
return -1;
}
// b 表示模式串,m 表示长度,suffix,prefix 数组事先申请好了
private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix) {
for (int i = 0; i < m; ++i) { // 初始化
suffix[i] = -1;
prefix[i] = false;
}
for (int i = 0; i < m - 1; ++i) { // b[0, i]
int j = i;
int k = 0; // 公共后缀子串长度
while (j >= 0 && b[j] == b[m - 1 - k]) { // 与 b[0, m-1] 求公共后缀子串
--j;
++k;
suffix[k] = j + 1; //j+1 表示公共后缀子串在 b[0, i] 中的起始下标
}
if (j == -1) prefix[k] = true; // 如果公共后缀子串也是模式串的前缀子串
}
}
// a,b 表示主串和模式串;n,m 表示主串和模式串的长度。
public int bm2(char[] a, int n, char[] b, int m) {
int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
generateBC(b, m, bc); // 构建坏字符哈希表
int[] suffix = new int[m];
boolean[] prefix = new boolean[m];
generateGS(b, m, suffix, prefix);
int i = 0; // j 表示主串与模式串匹配的第一个字符
while (i <= n - m) {
int j;
for (j = m - 1; j >= 0; --j) { // 模式串从后往前匹配
if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是 j
}
if (j < 0) {
return i; // 匹配成功,返回主串与模式串第一个匹配的字符的位置
}
/*坏字符移动step*/
int x = j - bc[(int) a[i + j]];
int y = 0;
if (j < m - 1) { // 如果有好后缀的话
y = moveByGS(j, m, suffix, prefix);
}
i = i + Math.max(x, y);
}
return -1;
}
// j 表示坏字符对应的模式串中的字符下标 ; m 表示模式串长度
private int moveByGS(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;
}
private static String logTemplate = "主串:[%s],模式串:[%s],起始位置:%s";
public static void main(String[] args) {
String mainStr = "aaaafds";
String compStr = "aaf";
System.out.println(String.format(logTemplate, mainStr, compStr, new BMStringOther().bm(mainStr.toCharArray(), mainStr.length(), compStr.toCharArray(), compStr.length())));
System.out.println(String.format(logTemplate, mainStr, compStr, new BMStringOther().bm2(mainStr.toCharArray(), mainStr.length(), compStr.toCharArray(), compStr.length())));
}
}public class BMStringOther {
private static final int SIZE = 256; // 全局变量或成员变量
private void generateBC(char[] b, int m, int[] bc) {
for (int i = 0; i < SIZE; ++i) {
bc[i] = -1; // 初始化 bc
}
for (int i = 0; i < m; ++i) {
int ascii = (int) b[i]; // 计算 b[i] 的 ASCII 值
bc[ascii] = i;
}
}
public int bm(char[] a, int n, char[] b, int m) {
int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
generateBC(b, m, bc); // 构建坏字符哈希表
int i = 0; // i 表示主串与模式串对齐的第一个字符
while (i <= n - m) {
int j;
for (j = m - 1; j >= 0; --j) { // 模式串从后往前匹配
if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是 j
}
if (j < 0) {
return i; // 匹配成功,返回主串与模式串第一个匹配的字符的位置
}
// 这里等同于将模式串往后滑动 j-bc[(int)a[i+j]] 位
// 想想为什么这里可以?
// 首先这里从BC中拿到的就是匹配模式串中最近的下标。【所有模式串hash化是可以从后->前覆盖的】
i = i + (j - bc[(int) a[i + j]]);
}
return -1;
}
// b 表示模式串,m 表示长度,suffix,prefix 数组事先申请好了
private void generateGS(char[] b, int m, int[] suffix, boolean[] prefix) {
for (int i = 0; i < m; ++i) { // 初始化
suffix[i] = -1;
prefix[i] = false;
}
for (int i = 0; i < m - 1; ++i) { // b[0, i]
int j = i;
int k = 0; // 公共后缀子串长度
while (j >= 0 && b[j] == b[m - 1 - k]) { // 与 b[0, m-1] 求公共后缀子串
--j;
++k;
suffix[k] = j + 1; //j+1 表示公共后缀子串在 b[0, i] 中的起始下标
}
if (j == -1) prefix[k] = true; // 如果公共后缀子串也是模式串的前缀子串
}
}
// a,b 表示主串和模式串;n,m 表示主串和模式串的长度。
public int bm2(char[] a, int n, char[] b, int m) {
int[] bc = new int[SIZE]; // 记录模式串中每个字符最后出现的位置
generateBC(b, m, bc); // 构建坏字符哈希表
int[] suffix = new int[m];
boolean[] prefix = new boolean[m];
generateGS(b, m, suffix, prefix);
int i = 0; // j 表示主串与模式串匹配的第一个字符
while (i <= n - m) {
int j;
for (j = m - 1; j >= 0; --j) { // 模式串从后往前匹配
if (a[i + j] != b[j]) break; // 坏字符对应模式串中的下标是 j
}
if (j < 0) {
return i; // 匹配成功,返回主串与模式串第一个匹配的字符的位置
}
/*坏字符移动step*/
int x = j - bc[(int) a[i + j]];
int y = 0;
if (j < m - 1) { // 如果有好后缀的话
y = moveByGS(j, m, suffix, prefix);
}
i = i + Math.max(x, y);
}
return -1;
}
// j 表示坏字符对应的模式串中的字符下标 ; m 表示模式串长度
private int moveByGS(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;
}
private static String logTemplate = "主串:[%s],模式串:[%s],起始位置:%s";
public static void main(String[] args) {
String mainStr = "aaaafds";
String compStr = "aaf";
System.out.println(String.format(logTemplate, mainStr, compStr, new BMStringOther().bm(mainStr.toCharArray(), mainStr.length(), compStr.toCharArray(), compStr.length())));
System.out.println(String.format(logTemplate, mainStr, compStr, new BMStringOther().bm2(mainStr.toCharArray(), mainStr.length(), compStr.toCharArray(), compStr.length())));
}
}
