当前位置: 首页 > news >正文

字符串模式匹配之KMP算法的理解和应用

模式串匹配过程

设匹配串为s,对应的移动指针i;模式串为p,对应的移动指针为j

补素的匹配过程

下面是补素的匹配过程,且模式串匹配成功,前两次比较,遇到p中的字符c时,就匹配失败了,模式串回退到0,同时匹配串移动到下一个位置开始匹配:

第一次比较:s[0] == p[0],s[1] == p[1], s[2] != p[2],比较3次
匹配串s:[a, a, a, a, c]
模式串p:[a, a, c]第一次比较:s[1] == p[1],s[2] == p[1], s[3] != p[2],比较2次
匹配串s:[a, a, a, a, c]
模式串p:   [a, a, c]第三次比较:s[2] == [p2],s[3] == p[1],s[4] != p[2],比较3次
匹配串s:[a, a, a, a, c]
模式串p:      [a, a, c]

优化的匹配过程

从上帝视角来看,可以将上面的过程优化成如下的比较过程,要吧明显知道,在前两次比较时,遇模式串的字符c时,可以通过右对齐模式串,更快的得到匹配结果:

第一次比较:s[0] == p[0],s[1] == p[1],s[2] != p[2],比较3次
匹配串s:[a, a, a, a, c]
模式串p:[a, a, c]
通过比较,发现模式串字符`c`所在的位置不同,匹配失败。第二次比较:s[2] == p[1],s[3] != p[2],比较2次
匹配串s:[a, a, a, a, c]
模式串p:   [a, a, c]
此时字符c所在的位置不相同,但由于s的子串[a, a, a](下标是[0,1,2])后缀与模式串的前缀相同,因此可以右移模式串,对齐如下:0  1  2
[a, a, a][a, a, c]第三次比较:s[3] == p[1],s[4] == p[2],比较2次
匹配串s:[a, a, a, a, c]
模式串p:      [a, a, c]
同理第二次的比较过程,得到如下的对齐:0  1  2  3
[a, a, a, a][a, a, c]

从优化后的过程,可以得到如下的基本事实(当遇到不匹配的字符时,对应匹配串中的最右下标i、最左下标k,模式串的下标j

  1. 如果匹配串与模式串的前缀子串成功匹配时,必然是匹配串的后缀子串与模式串的前缀子串匹配。
  2. 不可能出现在匹配串的区间[k, i)中,存在一个子串,完全匹配模式串。(反证法,如果存在,则匹配串s不可能迁移到i位置)
  3. 不可能出现在匹配串的区间[k+m, i]中,存在一个比已经匹配成功的模式串的子串[0,j]区间,更长的匹配子串,而且最长的匹配串的区间只可能为[k+1, i],且s[i] 与p[j-1]对齐 。(显然s[i] != p[j]时,最好的情况是区间s[j+1, i]的子串与模式串的前缀匹配,相当于模式串整体右移一个字符,此时匹配的子串长度就是j)
  4. 一旦匹配串的当前字符与模式串不匹配时,找到下一个可以匹配的子串的方法,是找到匹配串的后缀与模式串的前缀的公共子串。

KMP算法的思想

通过优化后上帝视角匹配过程,可以得到一个结论,要想快速确定模式串的匹配性,可以在遇到不匹配的字符时,记下标为i,保持匹配串的当前位置不变(匹配串的后缀右边界),右移模式串,找到下一个、最长的公共子串(匹配串的后缀与模式串的前缀相同的子串),并从匹配串的i位置继续匹配即可。

现在的问题是如何确定后缀前缀最长公共子串,即要右移模式串的距离?
实际上这里有一个角色转换思想,由于是在遇到不相等的字符时,才需要找下一个最长的公共子串,此时模式串与匹配串的前缀肯定是相同的,找匹配串的后缀与模式串的前缀的公共子串,就可以转换为找模式串的后缀与模式串的前缀的公共子串,一旦知道了公共子串的长度,就可以基于这个长度右移模式串,对齐到相同的长度!!

同时考虑到新找到的公共子串来自于模式串,而且最后一个字符只有与p[j]不相同时,才有可能和s[i]字符相同,因此在基于模式串构建后缀前缀公共子串的信息时,实际的子串的最后一个字符应该是恰好与p[k]不同的字符!!

KMP算法的优势

时间复杂度

如果模式串和匹配串中存在大量的重复子串,那补素算法的时间复杂度为O(m*n),但模式串的时间复杂度接近O(m+n),这是由于匹配串(主串)不需要回溯,同时模式串通过回跳表快速对齐,减少了大量的回溯次数,因此KMP算法的优势是显而易见的;但一般情况下,主串和模式串是无序的,因此即使是补素的算法,时间复杂度也能接近O(m+n)。

空间复杂度

KMP空间复杂度O(m),取决于模式串的nexts数组的存储,而补素的算法为O(1)。
因此在实际使用中,如果存储是瓶颈,则需要具体情况进一步优化KMP。

应用

KMP算法适用于大规模字符串匹配问题,例如:
文本搜索:快速定位特定模式在文档中的位置。
生物信息学:DNA序列比对。
网络协议分析:检测特定数据包模式。

算法实现

public int[] constructNexts(String str) {int pre_j = -1; // 模式串的指针int cur_i = 0; // 匹配串的指针char[] cs = str.toCharArray();int[] nexts = new int[str.length()];while (cur_i < str.length() - 1) { // 最后一个字符不关心,不论它是否在某个公共子串中,都需要进行一次比较if (pre_j == -1 || cs[cur_i] == pre_j) {// 注意这里是在cur_i处登记公共子串的长度,如此在查找匹配串的过程中,遇到不匹配的字符时,// 就直接通过nexts[j],使模式串的前一个公共前缀子串+下一个不等字符,恰好对齐到原匹配串的当前位置。// 当pre_j==-1时,此时比较的模式串的第一个字符,它的公共串的长度为0nexts[cur_i + 1] = pre_j + 1;// 比较下一个字符cur_i++; pre_j++;} else {// 当遇到不匹配的字符时,需要令pre_j回跳到,以子串[0, pre_j]为基准时记录的最长公共前缀,// 并期望cs[nexts[pre_j]] == cs[cur_i],那么就可以基于新的公共子串继续匹配cs[cur_i]以及之后的字符。pre_j = nexts[pre_j];}}
}public boolean match(String str, String pattern) {int[] nexts = constructNexts(pattern);char[] scs = str.toCharArray();char[] pcs = pattern.toCharArray();int i = 0; // 匹配串str上的指针int j = 0; // 模式串pattern上的指针while (i < str.length() && j < pattern.length()) {if (j == -1 || scs[i] == pcs[j]) {// j == -1时,只可能是由于scs[i] != pcs[0]时出现,因此// 需要同时增大i,j,从匹配串的下一个字符开始全新的匹配过程。i++; j++;} else {// 基于nexts数组,右对齐模式串j = nexts[j];}}// 只有当模式串走完时,说明匹配成功return j >= pcs.length;
}

相关文章:

  • ​​智能制造中的预测性维护:基于深度学习的设备故障预测​​
  • day006-实战练习题-参考答案
  • spring中的@Configuration注解详解
  • 操作系统学习
  • 小米MiMo推理大模型开源:7B参数规模超越更大规模模型
  • 电子制造业智能化转型:APS高级排程软件如何破局效率革命
  • x-cmd install | Tewi - 终端里的 Transmission 掌控者,功能全面的 BT 下载管理工具!
  • VSCode Auto Rename Tag插件不生效
  • 一套SaaS ERP管理系统源码,支持项目二开商用,SpringBoot+Vue+ElementUI+UniAPP
  • MicroPython for esp32s3开发HX711称重模块指南
  • 管家婆易指开单如何设置零售开单
  • Git从入门到精通-第二章-工具配置
  • 在TensorFlow中,`Dense`和`Activation`是深度学习模型构建里常用的层
  • Socket-UDP
  • [Unity]设置自动打包脚本
  • [Survey] Image Segmentation in Foundation Model Era: A Survey
  • VBA代码解决方案第二十四讲:EXCEL中,如何删除重复数据行
  • 2025深圳杯东三省数学建模竞赛选题建议+初步分析
  • hadoop伪分布式模式
  • MySQL RR (Repeatable Read) 隔离级别规则细节
  • 近七成科创板公司2024年营收增长,285家营收创历史新高
  • 空调+零食助顶级赛马备战,上海环球马术冠军赛将焕新登场
  • 4月人民币对美元即期汇率微跌,今年以来升值0.48%
  • 中吉乌铁路重点控制性工程开工建设,包括三座隧道
  • 辽宁辽阳火灾事故饭店经营者已被控制,善后处置全面展开
  • 西班牙葡萄牙电力基本恢复