字符串匹配的暴力算法就不细说了,主要思想就是从头比较模式串和主串,如果当前的字符匹配就比较下一位;如果不匹配则模式串向后移动一位和主串的下一位开始比较。
我们下面来讲BM的算法流程,最后会贴出代码。
德克萨斯大学的 Robert S. Boyer 教授和 J Strother Moore 教授发明了这种算法 称为BM算法。
BM算法有个不同点:让"主串" 与 “模式串” 头部对齐,从尾部开始比较。
BM算法有2个规则:坏字符 和 好后缀
假定主串为 “HERE IS A SIMPLE EXAMPLE”,模式串为 “EXAMPLE”。
一、坏字符原则:
如果主串和模式串的相应的字符不匹配就叫坏字符,那么主串移动的位数等于: 坏字符在模式串中的位置 减去 坏字符在模式串中最右出现的位置;如果坏字符在模式串中找不到那么就 减去 -1。
首先,模式串的 E 和 主串的 S不相等,那么模式串移动的位数为: E在模式中中的位置为 6, 而S在模式串找不到,那么 移动位数等于 6 - (-1)= 7.
移动7位后的结果如下:
再比较模式串的 E 和 主串的 P,也不匹配,但是 P在模式串中最右的位数是4,所以模式串移动的位数为 6-4 = 2;
移动2位的结果如下:
然后依次比较发现MPLE都是相同的,到模式串的A 和 主串的 I 不匹配,即 I 为坏字符,I 在模式串中对应的位置为 2,而 I 不在模式串中 ,所以模式串移动的次数等于: 2- (-1)= 3。这里要注意了,按坏字符原则 模式串要移动3位,但是 按照好后缀的原则,模式串应该移动几位呢。
MPLE、PLE、LE、E都是好后缀,好后缀 移动的位数等于:好后缀的位置 - 搜索词中的上一次出现位置。
好后缀 MPLE 在 模式串的前面字符 EXM 中没有,PLE,在 EXAM 中也没出现,LE 在 EXAMP 中 也没出现,E 在模式串 的第 0 为出现了,所以,
移动的位数,好后缀 E 对应于 模式串的 第6 位,而 他上一次出现的位置是0,所以 移动的位数是 6 - 0 = 6; 而刚刚说的 坏字符 原则只能移动3位 。所以采用 好后缀 原则。
BM 算法就是 遇到 不匹配的字符,每次都会计算 坏字符 和 好后缀移动的步数取最大值。移动 6 位后的结果如下:
然后 比较 主串的 P 和模式串的E。
发现是 坏字符,根据 坏字符 移动的位数等于:P 对应模式串的位置是6, P在模式串中最右出现的位置是 4,移动 6 - 4 = 2 位;移动后的结果如下。
然后从最后一位开始比较,发现最后完全匹配上了,流程结束。
根据上面的原则,相应的代码如下:
public class BM {
/**
* 利用坏字符规则计算移动位数:坏字符 对应在模式串中的位置 - 从右往左搜索坏字符在模式串中的位置,
* 如果 坏字符在模式串中未找到,那么 就 减去 (-1)
*/
public static int badCharacter(String moduleString, char badChar, int badCharSuffix) {
return badCharSuffix - moduleString.lastIndexOf(badChar, badCharSuffix);//lastIndexOf 找不到就返回 -1, badCharSuffix 表示从badCharSuffix这个位置开始,往左边查找
}
/**
* 利用好后缀规则计算移动位数
*/
public static int goodPostSuffix(String moduleString, int goodCharSuffix) {
int result = -1;
// 模式串长度
int moduleLength = moduleString.length();
// 好字符数
int goodCharNum = moduleLength - 1 - goodCharSuffix;
for (; goodCharNum > 0; goodCharNum--) {
String endSection = moduleString.substring(moduleLength - goodCharNum, moduleLength);
String startSection = moduleString.substring(0, goodCharNum);
if (startSection.equals(endSection)) {
result = moduleLength - goodCharNum;
}
}
return result;
}
/**
* BM匹配字符串
*
* @param originString 主串
* @param moduleString 模式串
* @return 若匹配成功,返回下标,否则返回-1
*/
public static int match(String originString, String moduleString) {
// 主串
if (originString == null || originString.length() <= 0) {
return -1;
}
// 模式串
if (moduleString == null || moduleString.length() <= 0) {
return -1;
}
// 如果模式串的长度大于主串的长度,那么一定不匹配
if (originString.length() < moduleString.length()) {
return -1;
}
int moduleSuffix = moduleString.length() - 1;
int moduleIndex = moduleSuffix;
int originIndex = moduleSuffix;
int originLength = originString.length();
for (int i = originIndex; originIndex < originLength && moduleIndex >= 0;) {
char oc = originString.charAt(originIndex);
char mc = moduleString.charAt(moduleIndex);
if (oc == mc) {
originIndex--;
moduleIndex--;
} else {
// 坏字符规则
int badMove = badCharacter(moduleString, oc, moduleIndex);
// 好字符规则
int goodMove = goodPostSuffix(moduleString, moduleIndex);
// 下面两句代码可以这样理解,主串位置不动,模式串向右移动
originIndex = i + Math.max(badMove, goodMove);
moduleIndex = moduleSuffix;
// ot就是中间变量
i = originIndex;
}
}
if (moduleIndex < 0) {
// 多减了一次
return originIndex + 1;
}
return -1;
}
public static void main(String[] args) {
// 主串
String originString = "HERE IS A SIMPLE EXAMPLE";
// 模式串
String moduleString = "EXAMPLE";
int index = match(originString, moduleString);
System.out.println("匹配的下标:" + index);
}
}