数据结构--串的模式匹配算法

news2024/9/20 1:31:26

文章目录

  • 串的模式匹配算法
    • 1.朴素算法(Brute-Force(BF)暴力算法)
      • BF算法分析
    • 2.KMP算法
      • 字符串的最长公共前后缀
      • 部分匹配表(前缀表)Next

串的模式匹配算法

查找子串(模式串)在主串中的位置的操作通常称为串的模式匹配

1.朴素算法(Brute-Force(BF)暴力算法)

如果两个指针(i,j)指向的元素相同则指针后移,不相同则需要回退指针(主串指针回退到上次匹配首位的下一个位置,子串指针回退到开头位置),重复进行上述操作直到主串指针指向主串末尾,即如下所示:

在这里插入图片描述

在2、3、4步骤中,主串的首字符与子串的首字符均不等。在步骤1中主串与子串的前三个字符相等,这就意味着子串的首字符"g"不可能与主串的二、三位相等,故上图中步骤2、3完全是多余的。

也就是说,如果我们知道子串的首字符"g"与后面两个字符不相等(此处需要进行一些预处理),我们就不需要再进行2、3步操作,只保留1、4、5步即可。

在使用朴素算法进行匹配时,主串指针需要进行一些回退。而在知道了子串的一些性质后,主串指针不需要再进行回退,只需一直往前走就行,这样就节省了一些时间开销。

BF算法分析

1.算法在字符比较不相等,需要回溯即i = i - j + 1:即回退到s中的下一个字符开始进行字符匹配。
2.最好情况下的时间复杂度为O(m+n)
3.最坏情况下的时间复杂度为O(m*n)

2.KMP算法

Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP 算法”,常用于在一个文本串 S 内查找一个模式串 T 的出现位置,为了避免朴素算法的低效,由Donald Knuth、Vaughan Pratt、James H. Morris 三人于 1977 年联合发表,故取这 3 人的姓氏命名此算法。

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

字符串的最长公共前后缀

字符串 “ABABA” :

前缀的概念:指的是字符串的子串中从原串最前面开始的子串

前缀有:A,AB,ABA,ABAB

后缀的概念:指的是字符串的子串中在原串结尾处结尾的子串

后缀有:BABA,ABA,BA,A

公共前后缀:一个字符串的所有前缀连续子串和所有后缀连续子串中相等的子串

共前后缀有:A ,ABA

最长公共前后缀:所有公共前后缀的长度最长的那个子串

最长公共前后缀是: ABA

在这里插入图片描述

在得知了子串中有相等的前后缀之后,匹配失败时子串指针不需要回退到开头处,而是回退到相等前缀的后一个位置。

在这里插入图片描述

部分匹配表(前缀表)Next

从第一个字符开始的每个子串的最后一个字符该子串的最长公共前后缀的长度的对应关系表格

其实就是:每个子串的最大相等前后缀的长度

字符串T=“aabaaf "

子串 “a”:最后一个字符是 a,该子串没有前缀也没有后缀,最长公共前后缀长度是 0,因此对应关系就是 a - 0
子串 “aa”:最后一个字符是 a,该子串的最长公共前后缀长度是 1,因此对应关系就是 a- 1
子串 “aab”:最后一个字符是 b,该子串的最长公共前后缀长度是 0,因此对应关系就是 b- 0
子串 “aaba”:最后一个字符是 a,该子串的最长公共前后缀长度是 1,因此对应关系就是 a- 1
子串 “aabaa”:最后一个字符是 a,该子串的最长公共前后缀长度是 2,因此对应关系就是 a- 2
子串 “aabaaf”:最后一个字符是 f,该子串的最长公共前后缀长度是 0,因此对应关系就是 f- 0

所以我们能得到字符串T的前缀表为:

j012345
Taabaaf
next010120

作用:决定了子串指针j在匹配失败时回溯到的位置

在这里插入图片描述

KMP算法就可以整体上分为两步:

1.计算前缀表

import java.util.Arrays;
/**
 * 计算前缀表next
 */
public class Next {
    /**
         * 获取一个字符串 pattern 的部分匹配表
         *
         * @param patternStr 用于模式匹配字符串
         * @return 存储部分匹配表的每个子串的最长公共前后缀的 next数组
         */
    public int[] kmpNext(String patternStr) {
        //将 patternStr 转为 字符数组形式
        char[] patternArr = patternStr.toCharArray();

        //预先创建一个next数组,用于存储部分匹配表的每个子串的最长公共前后缀
        int[] next = new int[patternStr.length()];

        /*
            从第一个字符(对应索引为0)开始的子串,如果子串的长度为1,那么肯定最长公共前后缀为0
            因为这唯一的一个字符既是第一个字符,又是最后一个字符,所以前后缀都不存在 -> 最长公共前后缀为0
         */
        next[0] = 0;

        /*
          len有两个作用:
            1. 用于记录当前子串的最长公共前后缀长度
            2. 同时知道当前子串的最长公共前后缀的前缀字符串对应索引==>子串指针j在匹配失败时回溯到的位置
         */
        int len = 0;

        //从第二个字符开始遍历,求索引在 [0,i] 的子串的最长公共前后缀长度
        int i = 1;
        while (i < patternArr.length) {

            //比较一下 patternArr[len] 与 patternArr[i] 是否相等
            if (patternArr[len] == patternArr[i]) {
                /*
                    1.如果相等即 patternArr[len]==patternArr[i],那么就可以确定存在当前子串的最长公共前后缀
                    2.由于是拼接操作,那么当前子串的最长公共前后缀长度只需要在上一个子串的最长公共前后缀长度的基础上 +1 即可
                      即 next[i] = next[i-1] + 1 
                    3.由于 len 是记录的子串的最长公共前后缀长度, len 还是记录的上一个子串的最长公共前后缀长度,
                    因此:next[i] = next[i-1] + 1 等价于 next[i] = ++len
                 */
                next[i] = ++len;

                //判断以下一个字符结尾的子串的最长公共前后缀长度
                i++;
                //  A  B  C  D  A  B  D
                // [0, 0, 0, 0, 1, 2, 0]
            }else {
                /*
                    1.如果不相等 patternArr[len]!=patternArr[i]
                    2.可以先修改len的值:len = next[len-1],再去判断下一个字符是否相等,即 判断 patternArr[len] 是否等于 patternArr[i]
                    3.但实际上我们在这里改为 len = next[len-1] 表示上一个子串的最长公共前后缀字符串的长度
                 */
                if(len==0) {
                    /*
                        len为 0说明上一个子串已经没有了公共前后缀字符串
                        则我们没有继续寻找的必要,当前子串的最长公共前后缀字符串长度就是0
                     */
                    next[i] = len;

                    //继续寻找下一个字符串的最长公共前后缀字符串长度
                    i++;

                }else{
                    len = next[len - 1];
                }

            }
        }

        return next;
    }
}         
//测试
public static void main(String[] args) {
    Next next = new Next();
    String patternStr = "ABCDABD";
    System.out.println(Arrays.toString(next.kmpNext(patternStr)));
    //输出结果:[0, 0, 0, 0, 1, 2, 0]
}

2.根据前缀表移动两个指针进行匹配

/**  匹配成功一个就退出匹配
     * @param matchStr   原字符串
     * @param patternStr 子串
     * @param next       子串对应的部分匹配表
     * @return 如果是-1,就是没有匹配到,否则就返回第一个匹配的位置
     */
public  int search(String matchStr, String patternStr, int[] next) {

    int i = 0, j = 0;

    while (i < matchStr.length() && j < patternStr.length()) {
        // matchStr = "AABABADDABAC";
    	// patternStr = "BAB";
        if (matchStr.charAt(i) == patternStr.charAt(j)) {
            //相等就继续进行匹配
            i++;
            j++;

        } else {
            //如果 patternStr[i] 和 matchStr[j] 不相等

            if (j == 0) {
                /*
                        表示 matchStr 没有匹配到 patternStr的第一个字符
                        那直接将 matchStr 的指针 i 向后移动一位即可
                     */
                i++;
            } else {
                j = next[j - 1];
            }
        }

    }
    return j == patternStr.length() ? i - j : -1;
}

public static void main(String[] args) {
    KmpSearch kmpSearch = new KmpSearch();
    Next next = new Next();
    String matchStr = "AABABADDABAC";
    String patternStr = "BAB";
    int index = kmpSearch.search(matchStr, patternStr, next.kmpNext(patternStr));

    System.out.println("index = " + index);
    // 输出:index = 2
}

允许匹配多个,可重复索引字符的代码:

import java.util.ArrayList;

/**
 * kmp搜索算法
 * 允许匹配多个,可重复索引字符的代码
 */
public class Search {
    /**
     * @param matchStr   原字符串
     * @param patternStr 子串
     * @param next       子串对应的部分匹配表
     * @return 每次匹配成功的字符串的开始索引位置的集合
     */
    public  ArrayList<Integer> kmpSearch(String matchStr, String patternStr, int[] next) {

        int i = 0, j = 0;

        ArrayList<Integer> firstIndexList = new ArrayList<>();

        while (i < matchStr.length()) {

            if (matchStr.charAt(i) == patternStr.charAt(j)) {
                //相等就继续进行匹配
                i++;
                j++;

            } else {
                //如果 patternStr[i] 和 matchStr[j] 不相等

                if (j == 0) {
                    /*
                        表示 matchStr 没有匹配到 patternStr的第一个字符
                        那直接将 matchStr 的指针 i 向后移动一位即可
                     */
                    i++;
                } else {
                    j = next[j - 1];
                }
            }

            if (j == patternStr.length()) {
                //超出了最大索引值
                firstIndexList.add(i - j);
                j = next[j - 1];
            }

        }
        return firstIndexList;
    }
    
    public static void main(String[] args) {
        Next next = new Next();
        Search search = new Search();
        //原字符串
        String matchStr = "AABABADDABAC";
        //子串
        String patternStr = "ABA";
        ArrayList<Integer> arrayList = search.kmpSearch(matchStr, patternStr, next.kmpNext(patternStr));
        System.out.println(arrayList);
        // 输出:[1, 3, 8]
    }
}

算法动画页面效果可免费给哦!

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

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

相关文章

NET8 MAUIBlazor发布用于windows应用

1.打开 PowerShell 终端 , 命令行进入工程目录,以我的例子工程为例 DOS命令:cd 项目名 2.复制窗口里面的 Thumbprint 下的指纹码, 例如我这个是E18EF79CF31104139F16BD2089F4AB1898D381C2 3.配置项目生成设置, 双击项目名称或者直接编辑 ltyj.C2.Cilent.csproj 文件 添加下面…

C语言操作符详解(13)

文章目录 前言一、二进制和进制转换2进制转10进制10进制转2进制2进制转8进制2进制转16进制 二、原码、反码、补码三、移位操作符左移操作符右移操作符 四、位操作符&^~一道奇葩的面试题一道练习题再来一个练习题 五、逗号表达式六、结构成员访问操作符结构体结构的声明结构…

汇编调用C库函数—printf、scanf和Win32API

RadASM: ;右键 -> 生成依赖项 -> 生成自定义 -> masm .586 .model flat,stdcall option casemap:noneinclude msvcrt.inc include Kernel32.inc include User32.incincludelib msvcrt.lib includelib Kernel32.lib includelib User32.libscanf proto c :ptr,:vararg …

Java 的Swing 之JFrame快速入门

3、讲原件添加到当前窗口当中 //讲原件添加到当前窗口当中 this.add(btnlong); this.add(btnreset); this.add(usertext); this.add(passtext); this.add(username); this.add(password); this.setVisible(true); 4、对对应按钮设置动作监听 btnlong.addActionListene…

足球大小球及亚盘数据分析与机器学习实战详解:从数据清洗到模型优化

本文将深入探讨Java在数据分析和机器学习中的实际应用&#xff0c;涵盖数据预处理、模型训练和优化等方面的内容。通过详尽的代码示例&#xff0c;帮助读者掌握相关技术并应用于实际项目中。 数据分析、初盘数据、走地数据、分析管理系统、AI大模型预测系统、全自动化下单系统…

直播相关02-录制麦克风声音,QT 信号与槽,自定义信号和槽

一 信号与槽函数 #include "mainwindow.h" #include <QPushButton> #include <iostream> using namespace std;//我们的目的是在 window中加入一个button&#xff0c;当点击这个button后&#xff0c;关闭 MainWindow 。 MainWindow::MainWindow(QWidget …

【实施文档】软件项目实施方案(Doc原件2024实际项目)

软件实施方案 二、 项目介绍 三、 项目实施 四、 项目实施计划 五、 人员培训 六、 项目验收 七、 售后服务 八、 项目保障措施软件开发管理全套资料包清单&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&am…

【文献阅读】Unsupervised Machine Learning for Bot Detection on Twitter

Abstract 引入新特征&#xff0c;并降低所提模型的复杂性&#xff0c;从而提高基于聚类算法的机器人识别准确性。 最小化数据集维度和选择重要特征来实现的。 实验证明该方法的特征可以与四种不同的聚类技术&#xff08;agglomerating、k-medoids、DBSCAN 和 K-means&#x…

Android Graphics 显示系统 - VirtualDisplay mirrorDisplay 简单示例

“ Life is like a box of chocolates, you never konw what youre going to get。最近我也得到了一块巧克力&#xff0c;迫不及待地想尝一下甜的惊喜 。” 前言 上一篇文章&#xff0c;我们分享了一个VirtualDisplay的简单实例&#xff0c;主要是为了引入创建虚拟屏幕都使用了…

C# 如何检查两个给定的线段是否相交(How to check if two given line segments intersect)

给定两条线段(p1, q1)和(p2, q2)&#xff0c;判断给定的线段是否相交。 在讨论解决方案之前&#xff0c;让我们先定义方向的概念。平面中有序点三元组的方向可以是 –逆时针 –顺时针 –共线 下图显示了&#xff08;a&#xff0c;b&#xff0c;c&#xff09; 的不同可能方…

多进程批量下载era5再分析数据

1.配置key https://cds.climate.copernicus.eu/api-how-to 获取key 修改配置文件&#xff0c;把url和key复制进行 vim $HOME/.cdsapirc2.下载 根据要求修改年和月份等变量 import cdsapi import calendar import concurrent.futures import osdef download_month_data(year,…

KEIL编译生成.bin文件的简单方法

fromelf --bin -o "$LL.bin" "#L" 如图 如果不行请尝试其他方法

大模型算法入行转行?我建议你这样做!

最近私信问我关于入行、转行方面的问题比较多&#xff0c;就专门写一篇讲讲我的理解。 首先说明一下个人的背景和现状&#xff0c;我本人是本科学历&#xff0c;有互联网大厂搜推方向经验&#xff0c;后来跳到中厂继续做推荐&#xff0c;去年开始做大模型。现在是个小组长&…

c中 int 和 unsigned int

c语言中&#xff0c;char、short、int、int64以及unsigned char、unsigned short、unsigned int、unsigned int64等等类型都可以表示整数。但是他们表示整数的位数不同&#xff0c;比如&#xff1a;char/unisigned char表示8位整数&#xff1b; short/unsigned short表示16位整…

桂林自闭症寄宿学校:用关爱点亮未来

在桂林这座风景如画的城市中&#xff0c;隐藏着一所特别的学校&#xff0c;它以无尽的关爱与专业&#xff0c;为自闭症儿童撑起了一片希望的天空&#xff0c;这就是星贝育园自闭症儿童寄宿制学校。在这里&#xff0c;每一个孩子都是独一无二的&#xff0c;他们被温柔以待&#…

仪器计量校准的设备保养方法有哪些?

仪器校准、检定&#xff0c;是对设备和仪器进行校正和校验。与规范所再现的相应值相关联的一组检测&#xff0c;以规定精确测量仪器或检测系统所指示的值&#xff0c;及产品测量仪器和对照化学物质所隐含的值&#xff0c;是否符合所要求的标准。 仪器校准可能包括以下过程&…

postgresql|数据库|pg_repack和idle_in_transaction_session_timeout参数的关系

一、问题描述 在使用pg_repack这个工具做数据库的表膨胀清理过程中&#xff0c;经常会遇到类似这样的警告&#xff1a; 这里的警告表明在膨胀治理的时候&#xff0c;此表遇到了事务阻塞&#xff0c;而此时我们有三种选择&#xff0c;第一个选择是等待该事务结束&#xff0c;第…

在Excel里制作简单游戏界面

生成随机激活码 找工具箱 插入按钮 建宏 方法一&#xff1a;新建按钮的时候创建宏 方法二&#xff1a;右键->指定宏 VBA VBA代码界面 调整字体 VBA代码 Public str As String 存储激活码显示的字符 Public st As String 中间变量&#xff0c;用来替代随机数 Public ot…

[实践应用] 深度学习之激活函数

文章总览&#xff1a;YuanDaiMa2048博客文章总览 深度学习之激活函数 激活函数基本概念分类常见的激活函数2. Tanh/双曲正切激活函数3. ReLU激活函数4. Softmax激活函数 PyTorch中如何使用1. 线性激活函数2. 非线性激活函数SigmoidTanhReLULeaky ReLUParametric ReLU (PReLU) 使…

ThinkPHP Email功能如何配置才能发送邮件?

ThinkPHP Email发送流程&#xff1f;使用ThinkPHP发Email方法&#xff1f; ThinkPHP作为一款流行的PHP框架&#xff0c;提供了强大的Email功能&#xff0c;使得开发者能够轻松实现邮件发送。AokSend将详细介绍如何配置ThinkPHP Email功能&#xff0c;以确保邮件能够顺利发送。…