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

Java-KMP字符串匹配算法

        给两个字符串s和t,如何很快的知道s是否包含t(即t是否是s的子串)。暴力的方法,我们依次以s每个位置为头,去匹配t。

 public int find(String s, String t) {char[] ss = s.toCharArray();char[] tt = t.toCharArray();int i = 0;while (i < ss.length) {int j = 0;//以s的每个位置为头匹配int start = i;while (start < ss.length&& j < tt.length) {//有一个不等直接break,匹配下一个位置开头的串if (ss[start] != tt[j]) {break;}start++;j++;}//如果j来到了t字符串的长度,说明上面的循环第一次匹配上了,返回第一次匹配上的位置下标if (j == tt.length) {return start-tt.length;}//否则,从下一个位置开始匹配i++;}return -1;
}

        举例,s=ababeababde,t=ababd。我们看一下暴力解法的过程。

1、i=0,start=0,j=0去循环匹配。start=4j=4时,不匹配,break。start回退到1j回退到0位置

2、i=1,start=1,j=0去循环匹配。不匹配,break。start到2,j回退到0位置。

3、i=2,start=2,j=0去循环匹配。不匹配,start=4j=2时不匹配,break。start回退到3j回退到0位置

4、i=3,start=3,j=0去循环匹配。不匹配,break。start到4,j回退到0位置。

5、i=4,start=4,j=0去循环匹配。不匹配,break。start到5,j回退到0位置。

5、i=5,start=5,j=0去循环匹配。匹配完成。(省略中间过程)。

        从上述过程中可以看到,已经遍历过的位置会多次回退,无疑增加了时间复杂度。KMP算法过程和暴力遍历时相似的,只是在过程中对回退操作进行了优化,减少了回退。

        了解KMP算法前,我们先了解一个定义:最长前后缀相等的长度(前缀和后缀相等的最大长度),对于字符串t,我们生成一个next数组,数组每个位置表示该位置之前的字符串中前缀和后缀相等的最大长度(规定:不能取到字符串最大长度,如果该位置前面没有字符串,最长前后缀相等的长度为-1(人为规定))

        比如上述字符串t=ababd,其next数组为 int[] next=new int[]{-1,0,0,1,2}。假设我们已经有了next数组,我们再看一下上面例子的匹配过程怎么加速?(后续会将next数组如何生成)

1、以0位置开头去匹配,当s来到start=4位置的e时,t位置来到j=4位置的d时,发现不匹配,我们检查next数组,发现4位置的最长前后缀相等的长度(next[4]是2,那么t位置回退到j=next[4]=2,继续开始比较;【为什么可以回退到这进行比较呢?因为s的前4个字符 abab,t的前4个字符 abab 已经比较过了,是相等的,而j=4时next[4]告诉我们当前位置前面字符串的最长前后缀相等长度是2,也就是说t[1]=s[start-1],t[0]=s[start-2],所以这两个位置就不需要再比较了,start就不需要进行回退了,j位置也可以只回退到next[4]位置。】其实此时的含义是,看以start-2位置开头的字符串是否可以匹配出t。

2、start=4,j=2位置开始比较,发现不匹配,此时j=2位置的最长前后缀相等的长度(next[2])是0,所以t的位置回退到j=next[2]=0;

3、start=4,j=0位置开始比较,发现不匹配,此时j已经为0,j无法再回退,所以satrt++;

4、start=5,j=0位置开始比较,匹配完成(后续过程省略)。

        从上述过程我们可以看出,s的遍历位置没有回退。下面我们看一下KMP的代码。

public int KMP(String s,String t){if(s==null||n==null||s.length()<t.length()){return -1;}char[] ss=s.toCharArray();char[] tt=t.toCharArray();int N=tt.length;//获取t字符串的next数组 next[i]表示t字符串【i位置以前的最长前后缀相等长度】int[] next=getNextArray(t);int i=0;int j=0;while(i<ss.length){if(ss[i]==tt[j]){i++;j++;}else if(j>0){//t的位置可以回退,回退到当前位置的最长前后缀长度的下一个位置进行比较j=next[j];}else{//j=0&&ss[i]==tt[j]i++;}}return j==tt.length?i-j:-1;
}

        关键问题来了,我们如何得到next数组呢?根据规定next[0]=-1,next[1]=0,next[2]=tt[0]==tt[1]?1:0。那么我们考虑一个普遍位置i(即考虑t字符串0-i-1位置字符串的前缀后缀相等情况),假设next[i-1]位置已经计算好了。如下图所示。那么就有以下几种情况:

       1、tt[i-1]=tt[next[i-1]](tt[next[i-1]]即左侧的三角位置,next[i-1]标识的两段根据其定义知道相等),所以此时可得出 next[i]=next[i-1]+1;

        2、tt[i-1]!=tt[next[i-1]],那如何寻找下一个比较位置呢?我们知道▲位置的next[▲]是该位置的最长前后缀相等长度,那么下一个比较位置就是★标识的位置,而★=next[▲]。以此类推,直到来到0位置,如果还不相等,那就代表i位置以前字符串的最长前后缀相等长度为0。

          如此下去,我们得到字符串t的next数组。(最长前后缀相等长度数组)。下面请看代码。

public int[] getNextArray(String t){if (t.length() == 1) {return new int[]{-1};}if (t.length() == 2) {return new int[]{-1, 0};}char[] tt = t.toCharArray();int[] next = new int[t.length()];//规定值next[0] = -1;next[1] = 0;if (tt[0] == tt[1]) {next[2] = 1;}//从2位置开始计算int i = 2;int compareIndex=next[i-1];while(i<tt.length){if(tt[i-1]==tt[compareIndex]){//相等,next[i]就计算出来了,i++计算下一个位置,compareIndex++是next[i]的值//也是计算i+1位置时,第一个需要比较的位置,大家多画图就可以理解了next[i++]=compareIndex++;}else if(compareIndex>0){//计算下一个比较位置compareIndex=next[compareIndex];}else{//compareIndex==0&&tt[i-1]!=tt[compareIndex]next[i]=0;//求下一个位置i++;}}return next;
}

         到此整个KMP算法就结束了,了解了原理后,代码还是很好写的。

http://www.lryc.cn/news/521666.html

相关文章:

  • Vue3使用vue-count-to数字滚动模块报错解决方案
  • 【高阶数据结构】线段树加乘(维护序列)详细解释乘与加懒标记
  • replaceState和vue的router.replace删除query参数的区别
  • [USACO14JAN] Ski Course Rating G
  • 初步认识 Neo4j 图数据库
  • Qt中容器 QVector、QList、QSet和QMap 性能与用途比较
  • ASP.NET Core - 依赖注入(四)
  • 数学用语中 up to 的含义
  • Spring Boot + MyBatis-Flex 配置 ProxySQL 的完整指南
  • WEB攻防-通用漏洞_XSS跨站_权限维持_捆绑钓鱼_浏览器漏洞
  • 人工智能任务20-利用LSTM和Attention机制相结合模型在交通流量预测中的应用
  • Day04-后端Web基础——Maven基础
  • Hive SQL必刷练习题:留存率问题
  • 虚拟同步机(VSG)Matlab/Simulink仿真模型
  • 单头注意力机制(SHSA)详解
  • 【漏洞分析】DDOS攻防分析
  • JavaScript动态渲染页面爬取之Splash
  • 慧集通(DataLinkX)iPaaS集成平台-系统管理之UI库管理、流程模板
  • OpenCV相机标定与3D重建(59)用于立体相机标定的函数stereoCalibrate()的使用
  • 摄像头模块在狩猎相机中的应用
  • ruoyi-cloud docker启动微服务无法连接nacos,Client not connected, current status:STARTING
  • 代码随想录算法训练营第三十四天-动态规划-63. 不同路径II
  • 在一个sql select中作多个sum并分组
  • 家用电路频繁跳闸的原因及解决方法!
  • 我的年度总结
  • ASP.NET Core 多环境配置
  • docker 安装mongodb
  • 完整地实现了推荐系统的构建、实验和评估过程,为不同推荐算法在同一数据集上的性能比较提供了可重复实验的框架
  • DRV8311三相PWM无刷直流电机驱动器
  • Mysql--运维篇--备份和恢复(逻辑备份,mysqldump,物理备份,热备份,温备份,冷备份,二进制文件备份和恢复等)