在字符串匹配算法中,KMP算法并不是效率最高的,这里将介绍的是BM算法。BM算法是指模式串与源文本串的比较从右到左,其本质是对后缀蛮力匹配算法的改进。该算法在最坏情况下的时间复杂度为O(N)。
前提基础
了解什么是坏字符,什么是好后缀。
- 坏字符:当源文本串中的某个字符跟模式串的某个字符不匹配时,称源文本串中的这个失配字符为坏字符。
- 好后缀:源文本串与模式串具有的最长的相同后缀串。
图示说明如下:
BM算法
BM算法定义了如下两个规则:
- 坏字符规则:当出现坏字符时,模式串需要向右移动,后移位数 = 坏字符出现时模式串匹配到的位置 - 坏字符在模式串中上一次出现的位置,当”坏字符“不包含在模式串中,则上一次出现的位置为 -1。
- 好字符规则:当匹配失败时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串中上一次出现的位置,若好后缀在模式串中没有再次出现,则为 -1 。
好字符规则的产生,是为了在某些情况下,能加大模式串的移动距离。举例如下:红色字符为已比较完成的字符,当前进行蓝色字符(即 S串中“I”字符与 P串中“A”)的比较,结果是失效。
HERE IS A SIMPLE EXAMPLE <== S串
EXAMPLE <== P串
若此时按照坏字符规则,则 后移位数 = 2 - (-1)= 3位。
若此时按照好后缀规则,在所有的“好后缀”(MPLE,PLE,LE,E)中,只有“E”在P串的头部出现了,因此 后移位数 = 6 - 0 = 6位。所以,这种情况下 应该采用规则2进行移动。
综上,可以看出BM算法的基本思想就是,每次后移的位数是两个规则的较大值。
BM代码
public class BM {
/**
* 算法匹配
*/
public static int pattern(String pattern, String target) {
int tLen = target.length();
int pLen = pattern.length();
if (pLen > tLen) {
return -1;
}
int[] bad_table = build_bad_table(pattern);
int[] good_table = build_good_table(pattern);
for (int i = pLen - 1, j; i < tLen;) {
System.out.println("跳跃位置:" + i);
for (j = pLen - 1; target.charAt(i) == pattern.charAt(j); i--, j--) {
if (j == 0) {
System.out.println("匹配成功,位置:" + i);
// i++; // 多次匹配
// break;
return i;
}
}
i += Math.max(good_table[pLen - j - 1], bad_table[target.charAt(i)]);
}
return -1;
}
/**
* 字符信息表
*/
public static int[] build_bad_table(String pattern) {
final int table_size = 256;
int[] bad_table = new int[table_size];
int pLen = pattern.length();
for (int i = 0; i < bad_table.length; i++) {
bad_table[i] = pLen; //默认初始化全部为匹配字符串长度
}
for (int i = 0; i < pLen - 1; i++) {
int k = pattern.charAt(i);
bad_table[k] = pLen - 1 - i;
}
return bad_table;
}
/**
* 匹配偏移表。
*
* @param pattern
* 模式串
* @return
*/
public static int[] build_good_table(String pattern) {
int pLen = pattern.length();
int[] good_table = new int[pLen];
int lastPrefixPosition = pLen;
for (int i = pLen - 1; i >= 0; --i) {
if (isPrefix(pattern, i + 1)) {
lastPrefixPosition = i + 1;
}
good_table[pLen - 1 - i] = lastPrefixPosition - i + pLen - 1;
}
for (int i = 0; i < pLen - 1; ++i) {
int slen = suffixLength(pattern, i);
good_table[slen] = pLen - 1 - i + slen;
}
return good_table;
}
/**
* 前缀匹配
*/
private static boolean isPrefix(String pattern, int p) {
int patternLength = pattern.length();
for (int i = p, j = 0; i < patternLength; ++i, ++j) {
if (pattern.charAt(i) != pattern.charAt(j)) {
return false;
}
}
return true;
}
/**
* 后缀匹配
*/
private static int suffixLength(String pattern, int p) {
int pLen = pattern.length();
int len = 0;
for (int i = p, j = pLen - 1; i >= 0 && pattern.charAt(i) == pattern.charAt(j); i--, j--) {
len += 1;
}
return len;
}
}