BM算法
-
核心:坏字符串规则,好后缀规则
- 主串:n
- 模式串:m
-
坏字符串规则
- 模式串匹配顺序:从后往前
- 坏字符:主串中未匹配的字符
- si:坏字符对应模式串字符的下标
- xi:坏字符在模式串中的下标,不存在则为-1
- 模式串后移位数:si - xi
- xi存在多个值时,取最大值
- 最小时间复杂度:O(n/m),示例如下:3 - (-1)
- 主串: 111211121112
- 模式串:1111
- 问题:si - xi可能是负数,示例如下:0 - 3
- 主串:1111
- 模式串:2111
- 解决方案:好后缀规则
-
好后缀规则
- 好后缀:已经匹配的字符,记作{u}
- 在模式串中查找另外一个和{u}匹配的子串,记作{u*}
- 后移模式串,使{u}和{u*}对齐
- 如果{u*}不存在,则后移,移动距离为模式串长度
- 异常情况考虑:好后缀 子串是否和模式串前缀 子串匹配
- 字符串s的后缀子串:最后一个字符和s对齐的 子串,示例如下
- s:123
- s后缀子串:23,3
- 前缀子串:类似后缀子串
-
规则选取
- 分别计算坏字符串和好后缀的值,取大值当做后移的值
- 坏字符xi查找优化:散列表
-
实现
//主串a, 模式串b
function bm(a,b){
let al = a.length
let bl = b.length
let dict = {}
for(let i=0;i<bl;i++){
dict[b[i]] = i //相同值取大值
}
let suffix = []
let prefix = []
generateGS(b, suffix, prefix)
let i = 0
while(i <= al-bl){
let j
for(j = bl-1; j >= 0; j--){
if(a[i+j] != b[j]) break
}
if(j < 0) return i
let x = j - getXi(a[i+j])
let y = 0
if(j < bl - 1){ //有好后缀
y = moveByGS(j)
}
i += Math.max(x,y)
}
return -1
function getXi(v){
return dict[v] !== undefined ? dict[v] : -1
}
//j表示坏字符串对应的模式串中字符下标
function moveByGS(j){
let k = bl - 1 - j //好后缀长度
if(suffix[k] != -1) return j - suffix[k] + 1 //匹配好后缀的子串
//遍历好后缀子串:从第二个字符(j+2)到 最后(b1)
for(let r = j + 2; r < bl ; r++){
//后缀子串有对应的前缀
if(prefix[bl-r]){
return r
}
}
return bl
}
}
function generateGS(b,suffix,prefix){
let m = b.length
//初始化suffix,prefix
for(let i=0; i < m; i++){
suffix[i] = -1
prefix[i] = false
}
//遍历b,取每项值b[j],与最后一项b[m-1]比较
for(let i = 0; i < m-1; i++){
let j = i //后缀子串匹配的子串的起始坐标
let k = 0 //公共后缀子串长度
//当前项和最后一项相等后,继续往前比较
while(j >= 0 && b[j] == b[m-1-k]){
j-- //往前匹配
k++ //往前
suffix[k] = j + 1 //
}
if(j == -1) prefix[k] = true //后缀子串匹配到了前缀子串
}
}
//测试
bm('cabcabd','cabd') //3
bm('acabcbcbacabc','cbacabc') //6
bm('2111','1111') //-1