Java数据结构与算法----字符串匹配(KMP算法)

news2024/11/24 10:59:57

KMP算法简介

        是一种线性时间复杂度的字符串匹配、查找算法。

暴力实现字符串匹配

对于字符串的匹配,可以使用暴力进行匹配:

如图进行演示:(以a串 ABABABCAA 被b串 ABABC 匹配为例):

第一轮匹配:(从a串下标为0处开始比对,在下标4处出现了不同: A -> C)

a串ABABABCAA
b串ABABC
是否相同×

第二轮匹配:(从a串下标为1处开始比对,在下标1处出现了不同: B -> A)

a串ABABABCAA
b串ABABC
是否相同×

第二轮匹配:(从a串下标为2处开始比对)

a串ABABABCAA
b串ABABC
是否相同

结束!

代码如下:

    //     这个是暴力
    public static int find(String a,String b){
        for(int i = 0;i<a.length()-b.length();i++){
            for(int j = 0; j<b.length();j++){
                if(a.charAt(i+j) != b.charAt(j)){
                    break;
                }
                if(j == b.length()-1){
                    return i;
                }
            }
        }
        return -1;
    }

但是 复杂度O(m*n)太高啦,显然 ⑧ 行。

KMP算法实现字符串匹配

首先我们对比一下之前的两个字符串:

a串ABABABCAA
b串ABABC
是否相同×

在出现不同的时候,我们其实已经知道了,ABAB已经和前面匹配了,所以我们只需要知道最长公共前后缀就好了,such as : ABAB中 AB = AB 就是 2个相同的长度:

ABABC
00120

这就是它的公共前后缀。也可以叫做next数组,(有的版本的kmp喜欢用整体向右移动,第一位设置为-1的表示法。其实都行)

我们先不考虑怎么求出这个next数组,我们先来看看已知next数组的话,怎么写我们的kmp代码:

逻辑如下:

假设i为a串的下标,j为b串的下标。

第一轮正常匹配(下标4出现错误,这时我们令j  =next[3] = 2,也就是说我们不需要重置i的位置,保证O(n),接下来比对a串i位置的字符和b串j位置的字符开始比较,如第二轮):

a串ABABABCAA
b串ABABC
是否相同×

第二轮:

a串ABABABCAA
b串ABABC
是否相同

这样子的话,复杂度就降低了。代码如下:

    public static int kmp(String a,String b){ 
        int next[] = getNext(b);         //先求出next数组

        int i = 0; //主串a的指针
        int j = 0; //子串b的指针
        while(i < a.length()){
            if(a.charAt(i) == b.charAt(j)){
                i++;
                j++;
            }
            else if(j > 0){ //匹配失败,根据next跳过
                j = next[j-1];
            }else{ //子串第一个匹配就失败了
                i++;
            }

            if(j == b.length()){ //匹配成功了
                return i-j;
            }
        }

        return -1;
    }

接下来就是求出这个next数组

对于ABABC,找出最长公共前后缀,可以用暴力来找,但是复杂度并不好,解决的复杂度的方法就是使用递推,我们在遍历字符串的时候,同样定义最长的相同前后缀作为下一个next的值,具体看代码理解:

    private static int[] getNext(String b) {
        int next[] = new int[b.length()];
        next[0] = 0;
        int i = 1;
        int prefix_len = 0; //当前公共前后缀长度
        while (i < b.length()) {
            if (b.charAt(i) == b.charAt(prefix_len)) {
                prefix_len++;
                next[i] = prefix_len;
                i++;
            } else if (prefix_len == 0) {
                next[i] = 0;
                i++;
            } else {
                prefix_len = next[prefix_len - 1]; //回退
            }
        }
        return next;
    }

所有代码:

package 算法;

import java.util.Scanner;

/**
 * @Author: stukk
 * @Description:
 * @DateTime: 2023-11-11 14:36
 **/
public class kmpDemo {
    //  匹配两个字符串,如果a中存在b,那么返回下标,否则返回-1
    public static void main(String[] args) {
        //匹配字符串
        Scanner cin = new Scanner(System.in);
        String a = cin.nextLine();
        String b = cin.nextLine();
        System.out.println(kmp(a, b));

    }

    //    这个是kmp
    public static int kmp(String a, String b) {
        int next[] = getNext(b);         //先求出next数组

        int i = 0; //主串a的指针
        int j = 0; //子串b的指针
        while (i < a.length()) {
            if (a.charAt(i) == b.charAt(j)) {
                i++;
                j++;
            } else if (j > 0) { //匹配失败,根据next跳过
                j = next[j - 1];
            } else { //子串第一个匹配就失败了
                i++;
            }

            if (j == b.length()) {
                return i - j;
            }
        }

        return -1;
    }

    private static int[] getNext(String b) {
        int next[] = new int[b.length()];
        next[0] = 0;
        int i = 1;
        int prefix_len = 0; //当前公共前后缀长度
        while (i < b.length()) {
            if (b.charAt(i) == b.charAt(prefix_len)) {
                prefix_len++;
                next[i] = prefix_len;
                i++;
            } else if (prefix_len == 0) {
                next[i] = 0;
                i++;
            } else {
                prefix_len = next[prefix_len - 1]; //回退
            }
        }
        return next;
    }

    //     这个是暴力
    public static int find(String a, String b) {
        for (int i = 0; i < a.length() - b.length(); i++) {
            for (int j = 0; j < b.length(); j++) {
                if (a.charAt(i + j) != b.charAt(j)) {
                    break;
                }
                if (j == b.length() - 1) {
                    return i;
                }
            }
        }
        return -1;
    }

}

例题

现在我们可以用一道稍微变形的题来训练一下:

P3375 【模板】KMP - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题目描述

给出两个字符串 s1​ 和 s2​,若 s1​ 的区间 [l,r] 子串与 s2​ 完全相同,则称 s2​ 在 s1​ 中出现了,其出现位置为 l。
现在请你求出 s2​ 在 s1​ 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2​,你还需要求出对于其每个前缀 ′s′ 的最长 border ′t′ 的长度。

输入格式

第一行为一个字符串,即为 s1​。
第二行为一个字符串,即为 s2​。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2​ 在 s1​ 中出现的位置。
最后一行输出∣s2​∣ 个整数,第 i 个整数表示 s2​ 的长度为 i 的前缀的最长 border 长度。

输入输出样例

输入 #1复制

ABABABC
ABA

输出 #1复制

1
3
0 0 1 

AC代码:

import java.io.*;
import java.util.Scanner;

/**
 * @Author: stukk
 * @Description:
 * @DateTime: 2023-11-11 17:06
 **/
public class Main {
    private static BufferedReader bf = new BufferedReader(new InputStreamReader(System.in));
    private static PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
    private static int aLen,bLen;
    public static void main(String[] args) throws IOException {
        String a = bf.readLine();
        String b = bf.readLine();
        aLen = a.length();
        bLen = b.length();
        kmp(a,b);
        pw.flush();
    }

    private static void kmp(String a, String b) {
        int next[] = getNext(b);
        int l = 0;
        int r = 0;
        while(l < aLen){
            if(a.charAt(l) == b.charAt(r)){
                l++;
                r++;
            }else if(r > 0){
                r = next[r-1];
            }else{
                l++;
            }

            if(r == bLen){
                int ans = l - r + 1;
                pw.println(ans);
                r = next[r-1];
            }
        }



        for(int i = 0;i<next.length;i++){
            pw.print(next[i]+" ");
        }


    }


    private static int[] getNext(String b) {
        int next[] = new int[bLen];
        next[0] = 0;
        int prefix = 0;
        int i = 1;
        while(i < bLen){
            if(b.charAt(i) == b.charAt(prefix)){
                prefix ++;
                next[i] = prefix;
                i++;
            }else{
                if(prefix == 0){
                    next[i] = 0;
                    i++;
                }else{
                    prefix = next[prefix - 1];
                }
            }
        }
        return next;
    }
}

稍微变一变代码就可以AC

总结

其实KMP算法也在告诉我们一个重要的道理:我们能够走的多远不取决于顺境时能走多块,而是取决于逆境时能多久找回曾经的自己。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1260814.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux静态库,共享库,计算机基础知识

1.库文件: 1).库文件库是一组预先编译好的方法的集合;Linux系统存储库的位置一般在/lib 和 /usr/lib (64位系统/usr/lib64)库的头文件放在/usr/include 2).库的分类 静态库:libxxx.a(命名规则) 共享库:libxxx.so(命名规则) 3).准备文件: //add.c int add(int x,int y) { retu…

前端学习系列之html

目录 初识html 发展史 优势 W3C 标准 地址 格式 网页基本标签 标题标签 段落标签 换行标签 水平线标签 字体样式 注释和特殊符号 特殊符号 图像、超链接 图像 常见图像格式 格式 超链接 格式 重要属性 href&#xff1a;规定链接指向的页面的 URL target…

线上问题整理-ConcurrentModificationException异常

项目场景&#xff1a; 商品改价&#xff1a;商品改价中通过多线程批量处理经过 Lists.partition拆分的集合对象 问题描述 商品改价中通过多线程批量处理经过 Lists.partition拆分的集合对象&#xff0c;发现偶尔会报 java.util.ConcurrentModificationException: nullat jav…

Vue3 + Scss 实现主题切换效果

Vue3 Scss 实现主题切换效果 先给大家看一下主题切换的效果&#xff1a; 像这样的效果实现起来并不难&#xff0c;只是比较麻烦&#xff0c;目前我知道的有两种方式可以实现&#xff0c;分别是 CSS 变量、样式文件切换&#xff0c;下面是该效果的核心实现方法 CSS变量 给…

使用Docker compose方式安装Spug,并结合内网穿透实现远程访问

文章目录 前言1. Docker安装Spug2 . 本地访问测试3. Linux 安装cpolar4. 配置Spug公网访问地址5. 公网远程访问Spug管理界面6. 固定Spug公网地址 前言 Spug 面向中小型企业设计的轻量级无 Agent 的自动化运维平台&#xff0c;整合了主机管理、主机批量执行、主机在线终端、文件…

【数据结构初阶】单链表

各位读者老爷好&#xff0c;鼠鼠我又来了哈。鼠鼠我呀现在来基于C语言实现以下单链表&#xff0c;希望对你有所帮助&#xff01; 目录 1.链表的概念及结构 2.链表的分类 3.无头单向非循环链表的实现 3.1.单链表打印 3.2.单链表尾插 3.3.单链表头插 3.4.单链表尾删 3.5…

Idea空白目录自动折叠的问题

IDEA创建空白项目和文件夹会自动折叠的问题。 有时文件项目会自动折叠&#xff0c;折叠后&#xff0c;不仅不好找项目和文件&#xff0c;还容易造成特别低端的错误。 如图&#xff1a; 当我们要在example目录下创建文件时&#xff0c;很容易就在springgaopdemo下创建了。 因为…

正则表达式 通配符 awk文本处理工具

目录 什么是正则表达式 概念 正则表达式的结构 正则表达式的组成 元字符 元字符点&#xff08;.&#xff09; 代表字符. 点值表示点需要转义 \ r..t 代表r到t之间任意两个字符 过滤出小写 过滤出非小写 space空格 [[:space:]] 表示次数 位置锚定 例&#xff1a…

笔记十九*、选中高亮和嵌套路由使用

19.1 选中高亮 NavLink App.jsx import React from "react"; import {NavLink, useRoutes} from "react-router-dom"; import routes from "./routes/index.jsx"; import "./app.css"const App () > {const element useRoutes(…

“文件批量改名专家:轻松自定义重命名并智能导出文件信息“

在日常工作中&#xff0c;处理大量文件时&#xff0c;往往需要一款得力的文件批量改名工具来协助我们高效、有序地进行文件管理。今天&#xff0c;我要向大家介绍一款强大的文件批量改名工具&#xff0c;它不仅支持统一自定义重命名&#xff0c;还能将相关信息导出到表格中&…

一、Oceanbase基础

一、集群相关概念 集群&#xff1a;整个分布式数据库。Region&#xff1a;表示区域&#xff0c;是地域的逻辑概念&#xff0c;如1个城市&#xff0c;1个集群可以有多个Region&#xff0c;用于跨城市远 距离容灾。Zone&#xff1a;表示分区&#xff0c;是机房或机架的逻辑概念…

[PyTorch][chapter 1][李宏毅深度学习-AI 简介]

前言&#xff1a; 李宏毅深度学习从2017-2023的系列课程总结 内容 章节 强化学习 11 李宏毅机器学习 【2017】 40 李宏毅机器学习深度学习(完整版)国语 【2020】 119 李宏毅大佬的深度学习与机器学【2022】 90 李宏毅机器学习完整课程【2023】 43 总结 303 目录…

lenovo联想笔记本YogaPro 14s IRP8D 2023款(83BU)原装出厂Windows11预装OEM系统

链接&#xff1a;https://pan.baidu.com/s/1s7PcN-y8RyHSV7uJQzC5OQ?pwddy9y 提取码&#xff1a;dy9y 联想电脑原厂W11系统&#xff0c;自带所有驱动、出厂主题壁纸、系统属性专属LOGO标志、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16GB或以上的U盘…

百度手机浏览器关键词排名优化——提升关键词排名 开源百度小程序源码系统 附带完整的搭建教程

百度作为国内领先的搜索引擎&#xff0c;一直致力于为用户提供最优质的信息服务。在移动互联网时代&#xff0c;手机浏览器成为了用户获取信息的主要渠道。而小程序作为轻量级的应用程序&#xff0c;具有即用即走、无需下载等优势&#xff0c;越来越受到用户的青睐。然而&#…

C语言第三十四弹--矩形逆置

C语言实现矩阵逆置 逆置结果如图 思路&#xff1a;通过观察逆置结果&#xff0c;首先发现行数和列数都发生了调换。其次观察逆置前后数字对应的下标&#xff0c;逆置前数字对应下标为:[x][j] 逆置后数字对应下标为&#xff1a;[y][x]。综上&#xff0c;就可以实现矩阵逆置。 …

ChromeDriver最新版本下载与安装方法

关于ChromeDriver最新下载地址&#xff1a;https://googlechromelabs.github.io/chrome-for-testing/ 下载与安装 setp1&#xff1a;查看Chrome浏览器版本 首先&#xff0c;需要检查Chrome浏览器的版本。请按照以下步骤进行&#xff1a; 打开Chrome浏览器。 点击浏览器右上角…

设计模式—迪米特原则(LOD)

1.背景 1987年秋天由美国Northeastern University的Ian Holland提出&#xff0c;被UML的创始者之一Booch等普及。后来&#xff0c;因为在经典著作《 The Pragmatic Programmer》而广为人知。 2.概念 迪米特法则&#xff08;Law of Demeter&#xff09;又叫作最少知识原则&…

14 网关实战:网关聚合API文档

上节课介绍了网关层的认证鉴权,今天这节介绍一下网关层如何聚合API接口文文档。 为什么需要聚合API接口文档? 大型微服务系统模块众多,木谷博客系统就有9个,如果这些服务的接口地址没有一个统一,那么客户端将要保存每个服务的接口地址,这个肯定是不现实。 先来看一下A…

小航助学题库蓝桥杯题库stem选拔赛(22年3月)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSD…

Mo0n(月亮) MCGS触摸屏在野0day利用,强制卡死锁屏

项目:https://github.com/MartinxMax/Mo0n 后面还会不会在,我可就不知道了奥…还不收藏点赞关注 扫描存在漏洞的设备 #python3 Mo0n.py -scan 192.168.0.0/24 入侵锁屏 #python3 Mo0n.py -rhost 192.168.0.102 -lock 解锁 #python3 Mo0n.py -rhost 192.168.0.102 -unlock …