首页 > 搜索 > 前缀字符串匹配算法,字符串匹配 ---

前缀字符串匹配算法,字符串匹配 ---

互联网 2020-10-31 08:53:09
在线算命,八字测算命理

关于字符串匹配有很多算法, BF, BK, KMP, 但这些都不是这篇文章的重点. 文章的重点是另外一种更高效的算法 Boyer-Moore 算法, 一般文本编辑器中的查找功能都是基于它实现的.

前置内容

什么是字符串匹配? 它只不过是在一堆字符串中查找特定的字符串的行为, 就像下面的例子一样.

现在为了方便起见, 我们把一堆的字符串称为主串 main, 特定的字符串称为模式串 pattern, 查找过程就可以理解为不断移动 pattern 的过程. 我们从主串的第一个字符开始, 逐个匹配, 遇到不匹配的字符, 就将 pattern 移动一位,直到全部匹配上.

所以到这里, 我们就可以说, 更高效的字符串匹配就是更高效的模式串 pattern 移动.如何进行高效的移动就是算法的意义所在.

暴力匹配

如果我们用一般方法来完成字符串匹配, 我们可以用一个循环来完成. 试着想象, 我们把模式串抽象的想象为一个字符,同样的主串中的所有子串我也都抽象的想象为一个字符, 这个过程就变为了对一个字符的匹配, 代码就可以写成下面这样:

function search(main, pattern) {if (main.length === 0 || pattern.length === 0 || main.length < pattern.length) {return -1}for (let i = 0; i

原本我们是正着过来的, 但现在却需要返回去, 那为什么不从一开始我们就倒着进行匹配? 我们从模式串的末尾倒着匹配, 当发现主串中无法匹配的字符时, 我们就把这个字符称为坏字符.然后我们就可以像上面说的在模式串中找相等的字符, 然后移动. 如果模式串中没有相等的字符, 我们就将整个模式串移动到坏字符的后面.

好了, 现在我们知道了基本原理,但还有个问题没解决, 我们怎么知道模式串的前面有没有和坏字符相等的字符?这里我们就需要一个技巧性的预处理, 用一个散列表来存储模式串中的字符和下标, 这样就可以快速定位字符的位置, 下面是一个最简单的实现:

/** * * @param {String} pattern * @description 以 ascii 码作为下标, 存储坏字符串在模式串中的位置 */function generatebc(pattern) {const bc = new Array(265).fill(-1)for (let i = 0; i < pattern.length; i++) {const index = pattern[i].charCodeAt()bc[index] = i}return bc}/** * * @param {String} substr 主串中的子串 * @param {String} pattern 模式串 * @param {Array} bc * @description 查找坏字符串是查找主串中不匹配的字符在模式串中的位置 * 假如主串中坏字符的位置对应的模式串中的位置是 si, 我们在模式串中找到同样的字符位置在 xi, * * 那么 si - xi, 就是模式串要移动的位置。 */function findBadChar(substr, pattern, bc) {let len = substr.length - 1let j = -1 //记录坏字符主串中的下标let k = -1 // 记录模式串中对应的坏字符下标let badChar = '' // 记录坏字符for (let i = len; i >= 0; i--) {if (substr[i] !== pattern[i]) {j = ibadChar = substr[i]break}}if (j > 0) {k = bc[badChar.charCodeAt()]}return {patternBadCharIndex: k,mainBadCharIndex: j}}好后缀规则

假设我们现在有这样一个主字符串 aaaaaaaaaaaaa, 模式串为 baaa, 我们用坏字符规则来处理, 好了, 计算出来在模式串中的坏字符位置为 -1. 所以, 光凭坏字符是不够的, 我们需要另一种处理方式, 好后缀规则.

像上面的, 我们把已经匹配到的 bcab 字符串, 就称为好后缀, 现在我们就利用它来进行模式串的移动.

在这之前我们要明确两个概念, 后缀子串和前缀子串. 就拿 bcab 来讲, 它的后缀子串如下表:

前缀子串也是同理.

到这里我们就可以去了解好后缀处理的基本规则:

1. 找出好后缀的所有后缀子串 2. 找出模式串的所有前缀子串 3. 找到好后缀中最长的能和模式串的前缀子串匹配的后缀子串 注意:好后缀的后缀子串,本身也是模式串的后缀子串,所以我们可以利用这个在模式串中找到另外的对应匹配的字符

我们就可以移动模式串中和好后缀子串相等的字符串到对应的位置.

为了更高效的移动模式串, 我们同样需要一些预处理, 将原本的循环处理成散列表查询.

第一步: 引入 suffix 数组

这个非常简单, 假设我们在好后缀中的某个子串的长度为 k, 它在模式串中的前缀子串中有相等的, 且起始位置为 i, 那么我们就记录 suffix[k] = i, 如果不存在,我们就记录为 suffix[k] = -1.

第二步: 引入 prefix 数组

除了 suffix 数组之外,我们还需要另外一个 Boolean 类型的 prefix 数组,来记录模式串的后缀子串(好后缀的后缀子串)是否能匹配模式串的前缀子串.

/** * @description 处理好后缀 * @param pattern 模式串 * suffix: 用子串长度为 k 存储主串的好后缀{u} 对应的子串中 {u*} 对应的起始位置 * prefix:用子串长度为 k 存储 模式串中是否存在和好后缀相同的字符串*/function generateGS(pattern) {const len = pattern.lengthconst suffix = new Array(len).fill(-1)const prefix = new Array(len).fill(false)for (let i = 0; i < len - 1; i++) {let j = i;let k = 0;while (j >= 0 && pattern[j] === pattern[len - 1 - k]) {j--;k++;suffix[k] = j + 1}if (j === -1) {prefix[k] = true}}return {suffix,prefix}}

当我们有了有了这两个数组, 我们既可以确定模式串的滑动位数.

}/** * * @param { Number} badCharStartIndex坏字符的对应的模式串的下标 * @param { Number} patternLength 模式串的长度 * @param { Array} suffix * @param { Array} prefix */function moveByGS(badCharStartIndex, patternLength, suffix, prefix) {let k = patternLength - badCharStartIndex - 1 // 好后缀长度// 完全匹配if (suffix[k] !== -1) {return badCharStartIndex - suffix[k] + 1}// 部分匹配for (let r = badCharStartIndex + 2; r = 0 && pattern[j] === pattern[len - 1 - k]) {j--;k++;suffix[k] = j + 1}if (j === -1) {prefix[k] = true}}return {suffix,prefix}}function moveByGS(badCharStartIndex, patternLength, suffix, prefix) {let k = patternLength - badCharStartIndex - 1 // 好后缀长度// 完全匹配if (suffix[k] !== -1) {return badCharStartIndex - suffix[k] + 1}// 部分匹配for (let r = badCharStartIndex + 2; rmain.length) {return -1}const mainLen = main.lengthconst patternLen = pattern.lengthconst bc = generatebc(pattern)const { suffix, prefix } = generateGS(pattern)let step = 1// i, start index of main stringfor (let i = 0; i = 0; i--) {if (substr[i] !== pattern[i]) {j = ibadChar = substr[i]break}}if (j > 0) {k = bc[badChar.charCodeAt()]}return {patternBadCharIndex: k,mainBadCharIndex: j}}console.log('查询结果为: ' + Bm('yaoyaozhuona', 'zhuo'))

免责声明:非本网注明原创的信息,皆为程序自动获取互联网,目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责;如此页面有侵犯到您的权益,请给站长发送邮件,并提供相关证明(版权证明、身份证正反面、侵权链接),站长将在收到邮件12小时内删除。

相关阅读

一周热门

查看更多