一文理解kmp算法(java代码)

简介: 一文理解kmp算法(java代码)

kmp是为了解决什么问题?


字符串匹配问题,用来在主字符串中查找匹配字符串的位置


N, M :字符串的长度char s[N], p[M]:待匹配串 匹配串eg: s[N] = “ababa”, p[M] = “aba”判断 s[N] 中是否有p[M]这个子串,如果有,开始下标为多少?

解决方法


暴力算法


   for(int i = 1; i <= n; i++) {
    bool flag = true;
    for(int j = 1; j <= m;j++) {
        if(s[i+j-1] != p[j]) {
            flag = false;
            break;
         }         
      }
   }


时间复杂度:O(n*m)


kmp优化


对于模式串中已经匹配过的那些字符,如果我们能找到一些规律,将模式串多往后移动几位,而不是像暴力算法算法一样,每次把模式串移动一位,就可以提高算法的效率。kmp算法给我们提供的思路是:对于模式串,将每一个字符在匹配失败时可以向后移动的最大距离保存在一个next数组。这样当匹配失败时就可以按照next数组中保存的数字向后多移动几位。从而提高算法的效率。


在讲述之前,我们先摆出两个概念:


前缀:指的是字符串的子串中从原串最前面开始的子串,如abcdef的前缀有:a,ab,abc,abcd,abcde

后缀:指的是字符串的子串中在原串结尾处结尾的子串,如abcdef的后缀有:f,ef,def,cdef,bcdef

什么是前缀数组next[]

在KMP算法中有个关键的数组,叫做前缀数组,每一个子串有一个固定的next数组,它记录着字符串匹配过程中失败情况下可以向后多跳几个字符,其实也是子串的前缀和后缀相同的最长长度。



怎么求这个数组我们放在最后说,先说怎么使用这个前缀数组来实现kmp算法


算法思路


思路其实已经说过了,就是在暴力的算法的基础上,在匹配失败的时候往后多跳几位,而跳几位保存在前缀数组中。接下来我们看一下原理是什么样的,为什么前缀数组就可以作为跳几步的依据。举个例子,下图中已经写好了总串s和模式串p,模式串的前缀数组为[0,0,1,2,3],且所以下标都是从1开始。看图中当i=8,j=4时s[i] != p[j + 1],即将要匹配失败了,图中红色圈住的是子串的后缀。黄圈圈住的是前缀。蓝色圈圈住的是已经和后缀匹配过的部分,那么下一次将模式串后移prefix[j]=2位时,原来的前缀正好对着蓝色圈圈部分,因为前缀=后缀=蓝色圈圈部分,所以移动后的橙色部分就不用再判断了。


再用上一个双指针算法思路。i遍历总串s,j遍历模式串p,判断s[i] 和 p[j + 1]是否匹配。不匹配就将j重置为前缀数组中prefix[j]的值。匹配的话j忘后移动一位。当匹配了n个字符后即代表完全匹配。此时答案即为i-n,如果要继续搜索,要将j再置为prefix[j]。


为了方便写代码所有数组的下标都从1开始


// kmp匹配
for (int i = 1, j = 0; i <= m; i++) {
    while (j != 0 && s[i] != p[j + 1]) {
        j = prefix[j]; // s[i] != p[j + 1]即不匹配,则往后移动
    }
    if (s[i] == p[j + 1])
        j++; // 匹配时将j++进行下一个字符的匹配
    if (j == n) { 
        j = prefix[j]; // 完全匹配后继续搜索
        System.out.print(i - n + " ");
    }
}


怎么求前缀数组


前缀数组是kmp里面最难的部分,网上也有很多种求法。比如利用后一个元素和前面的元素之间存在数学公式关系来求,我们这里使用的方式是和上面的匹配过程类似的方法,也就是将前缀看作模式串,在p中匹配他。也就是字符串p自己找自己的匹配串。


完整代码


import java.io.*;
public class Main {
    static int N=100010;
    static int M=1000010;
    static char []p=new char[N];
    static int []ne=new int[N];
    static char []s=new char[M];
    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
        int n=Integer.parseInt(br.readLine());
        String s1= br.readLine();
        for (int i = 1; i <=n; i++) {
            p[i]=s1.charAt(i-1);
        }
        int m=Integer.parseInt(br.readLine());
        String s2= br.readLine();
        for (int i =1; i <=m; i++) {
            s[i]=s2.charAt(i-1);
        }
        //构建前缀数组ne[]
        for(int i=2,j=0;i<=n;i++){
            while (j!=0 && p[i]!=p[j+1]) j=ne[j];
            if(p[i]==p[j+1]) j++;
            ne[i]=j;
        }
        //kmp匹配
        for(int i=1,j=0;i<=m;i++){
            while (j!=0 && s[i]!=p[j+1]) j=ne[j];
            // 匹配时将j++进行下一个字符得匹配
            if(s[i]==p[j+1]) j++;
            if(j==n){
                //当匹配成功时继续往下匹配
                j=ne[j];
                bw.write(i-n+" ");
            }
        }
        bw.flush();
        br.close();
        bw.close();
    }
}
相关文章
|
5天前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
|
19天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
33 5
Java反射机制:解锁代码的无限可能
|
7天前
|
算法
分享一些提高二叉树遍历算法效率的代码示例
这只是简单的示例代码,实际应用中可能还需要根据具体需求进行更多的优化和处理。你可以根据自己的需求对代码进行修改和扩展。
|
15天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
48 3
|
21天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
59 10
|
16天前
|
分布式计算 Java MaxCompute
ODPS MR节点跑graph连通分量计算代码报错java heap space如何解决
任务启动命令:jar -resources odps-graph-connect-family-2.0-SNAPSHOT.jar -classpath ./odps-graph-connect-family-2.0-SNAPSHOT.jar ConnectFamily 若是设置参数该如何设置
|
15天前
|
Java
Java代码解释++i和i++的五个主要区别
本文介绍了前缀递增(++i)和后缀递增(i++)的区别。两者在独立语句中无差异,但在赋值表达式中,i++ 返回原值,++i 返回新值;在复杂表达式中计算顺序不同;在循环中虽结果相同但使用方式有别。最后通过 `Counter` 类模拟了两者的内部实现原理。
Java代码解释++i和i++的五个主要区别
|
18天前
|
算法 测试技术 开发者
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗
在Python开发中,性能优化和代码审查至关重要。性能优化通过改进代码结构和算法提高程序运行速度,减少资源消耗;代码审查通过检查源代码发现潜在问题,提高代码质量和团队协作效率。本文介绍了一些实用的技巧和工具,帮助开发者提升开发效率。
19 3
|
17天前
|
分布式计算 Java 开发工具
阿里云MaxCompute-XGBoost on Spark 极限梯度提升算法的分布式训练与模型持久化oss的实现与代码浅析
本文介绍了XGBoost在MaxCompute+OSS架构下模型持久化遇到的问题及其解决方案。首先简要介绍了XGBoost的特点和应用场景,随后详细描述了客户在将XGBoost on Spark任务从HDFS迁移到OSS时遇到的异常情况。通过分析异常堆栈和源代码,发现使用的`nativeBooster.saveModel`方法不支持OSS路径,而使用`write.overwrite().save`方法则能成功保存模型。最后提供了完整的Scala代码示例、Maven配置和提交命令,帮助用户顺利迁移模型存储路径。
|
23天前
|
搜索推荐 Java 数据库连接
Java|在 IDEA 里自动生成 MyBatis 模板代码
基于 MyBatis 开发的项目,新增数据库表以后,总是需要编写对应的 Entity、Mapper 和 Service 等等 Class 的代码,这些都是重复的工作,我们可以想一些办法来自动生成这些代码。
30 6