关于字符串匹配有很多算法, BF, BK, KMP, 但这些都不是这篇文章的重点. 文章的重点是另外一种更高效的算法 Boyer-Moore 算法, 一般文本编辑器中的查找功能都是基于它实现的.
前置内容
什么是字符串匹配? 它只不过是在一堆字符串中查找特定的字符串的行为, 就像下面的例子一样.
现在为了方便起见, 我们把一堆的字符串称为主串 main, 特定的字符串称为模式串 pattern, 查找过程就可以理解为不断移动 pattern 的过程. 我们从主串的第一个字符开始, 逐个匹配, 遇到不匹配的字符, 就将 pattern 移动一位,直到全部匹配上.
![3e658e03c66a5f683f7c8aefb4866687.png](https://img-blog.csdnimg.cn/img_convert/3e658e03c66a5f683f7c8aefb4866687.png)
所以到这里, 我们就可以说, 更高效的字符串匹配就是更高效的模式串 pattern 移动. 如何进行高效的移动就是算法的意义所在.
暴力匹配
如果我们用一般方法来完成字符串匹配, 我们可以用一个循环来完成. 试着想象, 我们把模式串抽象的想象为一个字符, 同样的主串中的所有子串我也都抽象的想象为一个字符, 这个过程就变为了对一个字符的匹配, 代码就可以写成下面这样:
function search(main, pattern) {
if (main.length === 0 || pattern.length === 0 || main.length < pattern.length) {
return -1
}
for (let i = 0; i <= main.length - pattern.length; i++) {
let subStr = main.slice(i, i + pattern.length) // 此处可以想象为一个字符
if (subStr === pattern) {
return i
}
}
return -1;
}
这个方法简单粗暴, 所以有个名副其实的称呼叫做暴力匹配算法.
虽然我们上面把主串的子串, 和模式串都抽象为一个字符, 但实际比较时, 我们还是需要另一个循环来比较每个字符. 于是有人想了个改进的方法, 对主串中的子串和模式串进行哈希, 这样就每次比较哈希值就可以了. 但是这种方法, 治标不治本, 并没有提高移动模式串的效率.
坏字符匹配
现在我们来说另外一种匹配方法, 倒着匹配.
为什么要倒着匹配? 仔细的想一下我们在正方向匹配的时候, 遇到不匹配的字符我们能做什么?我们可以将字符一位一位的移动进行暴力匹配, 也可以拿主串中不匹配的字符在模式串中找到相等的字符, 然后移动该字符到主串中对应的位置, 如下图.
![256de5316ac54d0eb6de7689d403952f.png](https://img-blog.csdnimg.cn/img_convert/256de5316ac54d0eb6de7689d403952f.png)
原本我们是正着过来的, 但现在却需要返回去, 那为什么不从一开始我们就倒着进行匹配? 我们从模式串的末尾倒着匹配, 当发现主串中无法匹配的字符时, 我们就把这个字符称为坏字符.