字符串匹配算法(KMP)

article/2025/9/21 12:02:58

问题:

       给你两个字符串  s 和 pat ,请你在 s 字符串中找出 pat 字符串出现的第一个位置(下标从 0 开始),如果不存在则返回-1。

1.暴力匹配算法。

          暴力匹配算法较好理解,其大致原理如图:

         

          当D和E不匹配的时候,pat模式串会向右移动一位,然后继续匹配。 

         一直这样匹配,直到匹配到模式串或者匹配不成功为止。

代码如下:

class Solution {public static int strStr(String s , String pat){for (int i = 0 ; i <= s.length() - pat.length() ; i++){int m = i;for(int n = 0 ; n < pat.length() ; n++){if(s.charAt(m) != pat.charAt(n)){break;}if(n == pat.length()-1){return i;}m++; }}return -1;}
}

2.KMP算法。

        KMP的优势在于它能够利用字符串匹配失败后的信息对下次匹配进行优化,而不是每次都

傻傻的对下一个字符进行匹配。

        但是如何利用匹配失败后的有效信息呢?

 如图:

         

         我们暂且用m指针指向字符串s匹配到的位置,用n指针来表示模式串pat匹配到的位置。

当我们匹配到第n个字符匹配失败时,说明模式串的前n-1个字符至少是匹配成功的。

        而这相同的前n-1个字符,就是我们在匹配失败后所掌握的信息

        我们来看下面这种情况:

        

当 S[m] 与 pat[n] 匹配失败时,如何利用匹配失败的信息? 

当匹配失败时,我们可以确定的是pat模式串的前 (n-1)个字符S字符串的前 (n-1)个字符是相同的。

这就是我们可以利用的信息。这样就相当于我们事先知道了要匹配的字符!!!

       

       既然我们知道了S与pat串的前n-1个字符相同,我们就不用每次都让m指针回退至第二个字符处,m指针可以不动,只让n指针指向它应该指向的位置(如果pat模式串从第一位就不匹配,我们需要让m指针后退一位)。

如图:

        

那么n指针应该指向哪里呢?

        对于两个相同的字符串,如图:

             

        我们已经知道它们的后一位匹配不成功,pat模式串需要往后移进行下一次的匹配。那么我们应该怎样确定后移的位数?

    

        根据后移的结果我们不难发现,黄框所框住的部分是相同的。这是因为我们在找到匹配的位置后,在pat模式串中,n指针前面的字符肯定与S后面的字符相同。

         在回到之前匹配失败的场景,我们在后移pat模式串时,只要满足匹配后S中已匹配的字符串中后面的一部分与pat串前面的一部分相同,由于我们已经确定了S与pat前 (n-1)个字符相同,而pat串又是确定的,可以直接用pat的前(n-1)个字符进行讨论。

                  

        对字符串ABAB,将除了最后一个字符,包含第一个的所有连续组合称为前缀;

如:A,AB,ABA 

        后缀也同理,如:B,AB,BAB

可以发现,它的最长公共前后缀为 AB,也就是说,我们在下次匹配的时候可以直接在第三个位置进行匹配。

如图:

        

由于每次匹配成功的位数不相同,我们可以制订一个表格来表示它的最长公共前后缀长度

模式串ABABD
最长公共前后缀长度00120

这样我们就能在匹配失败时知道n应该去的位置。但是某一位匹配失败要查它前一位的最长公共前后缀长度,而模式串的最后一位的最长公共前后缀长度没有用处,而如果pat模式串第一位与m指针指向的字符不匹配时又无法使m指针后移,不如直接设定一个next数组来表示:

模式串ABABD
next[]-10012

当next数组值为-1时,m指针后移一位。

由于next数组需要先通过pat字符串进行求出,所以我们可以这样设计代码:

public class matching {int next[] = null;private void setNext(String pat){//在这里对next数组进行实现。}public int strStr(String s,String pat){setNext(pat);//下面就是该算法的实现。int n = 0;for (int m = 0 ; m < s.length() ; m++){if(n == -1 || s.charAt(m) == pat.charAt(n)){n++;}else{m--;n = next[n];}if(n == pat.length()){return m-n+1;}}return -1;}
}

接下来讲讲next数组实现的算法:

next数组是由最长公共前后缀表演化而来,要想知道next数组,知道最长公共前后缀表就可以。

其实现算法如下:

    private void setNext(String pat){int[] next = new int[pat.length()];int[] tlc = new int[pat.length()];//tlc数组表示最长公共前后缀表for(int i = 1 , j = 0; i < pat.length() ; i++){while(j>0 && pat.charAt(i) != pat.charAt(j)){j = tlc[j-1];}//与KMP算法类似,找最长公共前后缀,再向后匹配。if(pat.charAt(i) == pat.charAt(j)){j++;}tlc[i] = j;}next[0] = -1;for(int x = 1 ; x < pat.length() ; x++){next[x] = tlc[x-1];}}

还有更为简单的实现方式,是直接实现next数组:

    private void GetNext(String pat){int j,k;j=0;k=-1;next[0]=-1;while (j<pat.length()-1) {if (k == -1 || pat.charAt(j) == pat.charAt(k)) {j++;k++;next[j] = k;} else {k = next[k];}}}

KMP算法的改进
为什么KMP算法这么强大了还需要改进呢?
我们来看一个例子:
字符串S = "aaaaabaaaaac"
模式串pat = "aaaaac"
这个例子中当‘b’与‘c’不匹配时应该‘b’与’c’前一位的‘a’比,这显然是不匹配的。'c’前的’a’回溯后的字符依然是‘a’。
我们知道没有必要再将‘b’与‘a’比对了,因为回溯后的字符和原字符是相同的,原字符不匹配,回溯后的字符自然不可能匹配。但是KMP算法中依然会将‘b’与回溯到的‘a’进行比对。这就是我们可以改进的地方了。我们改进后的next数组命名为:nextval数组。KMP算法的改进可以简述为: 如果a位字符与它next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是它自己a位的next值。 这应该是最浅显的解释了。

如字符串"ababaaab"的next数组以及nextval数组分别为:

下标01234567
子串

a

babaaab
next-10012311
nextval-10-10-1310

下面是实现nextval数组的代码:

    private void GetNextVal(String pat){int j=0,k=-1;nextval[0]=-1;while (j<pat.length()){if (k==-1 || pat.charAt(j)==pat.charAt(k)){j++;k++;if (pat.charAt(j)!=pat.charAt(k))nextval[j]=k;elsenextval[j]=nextval[k];}else  k=nextval[k];}}

至此KMP算法就讲完了。

        但我在网上查有关KMP资料的时候,找到了另一种KMP算法的实现方式,这种实现方式相对来说更为复杂,同时效率在有些情况更高。

        这里引入一个概念:DFA,即有限状态自动机

        不要听着觉得它很高大上,其实原理特别简单,通俗来讲就是如果一个东西,它遇见了下一个东西,会变成下一个状态。我们来看下面这个图:

现在pat模式串处于 n 状态,当下次遇到 'B' 字符的时候,n指针可以向后移一位,pat模式串会到达 (n+1) 状态。

但如果是下面这种情况呢?

        

当pat串处于n状态时,此时遇到'A'字符,是不匹配的,然后pat应该到哪个状态。

根据状态n和字符'A',便可以确定一个新的字符串,pat串的前n个字符为它的最长前缀,只需找到新字符串的最长公共前后缀便可

以确定pat串下次该匹配的位置。如图:

 这种方式有什么优点呢?

        我们可以发现,当m指针匹配不成功时,它会直接向右移动一位进行匹配而不是等待下次匹配。这就是比上个算法优化的地方。

        我们可以画个简易的状态关系图:

patABABD
A11313
B02 040
C00000
D00005
E00000

        这个图就是有限状态自动机,它可以根据pat模式串匹配到的位置和下次遇到的字符来确定下次pat模式串得状态(即n指针的位置),从而实现“自动”。

基于数组快速的查找速度,我们可以将所有用到的字符都写入这个二维数组里,一般情况我们实现前256个字符:

代码如下:

    public void dfa(String pat) {this.pat = pat;int M = pat.length();dfa = new int[M][256];dfa[0][pat.charAt(0)] = 1;int X = 0;for (int j = 1; j < M; j++) {for (int c = 0; c < 256; c++) {if (pat.charAt(j) == c)dfa[j][c] = j + 1;elsedfa[j][c] = dfa[X][c];}X = dfa[X][pat.charAt(j)];}}

其实现原理与next数组构造有异曲同工之处,其他代码的实现也与上一种方法大致相同。

        

        


http://chatgpt.dhexx.cn/article/x8o7XLNV.shtml

相关文章

字符串匹配算法KMP详细解释——深入理解

1. 前言 字符串匹配是一个经典算法问题&#xff0c;展开来讲各类问题多达几十种&#xff0c;有名称的算法也不下三十种&#xff0c;所以需要深入学习的东西有很多。这次我们来探讨一个最简单的问题&#xff0c;假设现在随机输入一个长度为m的主串T&#xff0c;另外输入一个长度…

几种常见的字符串匹配算法

转自http://www.360doc.com/content/14/0325/15/15064667_363609292.shtml 1. 朴素算法 朴素算法是最简单的字符串匹配算法&#xff0c;也是人们接触得最多的字符串匹配算法。代码一看就懂&#xff0c;在此不在赘述。 #include<stdio.h> #include<string.h> voi…

字符串匹配算法——JavaScript

字符串匹配算法——javascript 文章目录 字符串匹配算法——javascript字符串匹配BF算法 &#xff08;暴力匹配&#xff09; √KMP算法 √BM算法**坏字符规则**好后缀规则 Trid树&#xff08;字典树&#xff09;√ 字符串匹配 字符串匹配问题的形式定义&#xff1a; **文本&a…

数据结构与算法--字符串匹配算法

目录 概要 单模式与多模式的区别 单模式匹配算法 BF算法 概念 代码实现 时间复杂度 应用 RK算法 概念 代码实现 时间复杂度 应用 BM算法 概念 算法原理 代码实现 时间复杂度 应用 多模式匹配算法 Trie树 概念 Trie树的插入 Trie树的查找 代码实现 时间复杂度 应用 概要 字符…

简单高效的字符串匹配算法

Quick Search算法 算法简介 Quick Search算法属于Sunday算法的一种。Sunday算法由Daniel M Sunday在1990年提出。论文原文&#xff1a;A VERV FAST SU6STRINC SEARCH ALGORITHM 在论文中&#xff0c;作者提出了三个不同的算法&#xff1a;Quick Search算法、Maximal Shift算…

算法之字符串匹配

字符串匹配&#xff1a;设 S 和 T 是给定的两个串&#xff0c;在主串 S 中找到模式串 T 的过程称为字符串匹配&#xff0c;如果在主串 S 中找到模式串 T &#xff0c;则称匹配成功&#xff0c;函数返回 T 在 S 中首次出现的位置&#xff0c;否则匹配不成功&#xff0c;返回 -1。…

经典字符串匹配算法——KMP算法

KMP算法 KMP算法是一种高效的字符串匹配算法&#xff0c;在传统暴力遍历匹配的基础上做了一定的优化。 首先KMP算法的实现也是使用了回退思想&#xff0c;不过与暴力遍历不同&#xff0c;KMP的回退&#xff0c;是让子串进行匹配&#xff0c;而不是主串。 KMP示例 首先我们来…

16.算法之字符串匹配算法

前言 字符串匹配是我们在程序开发中经常遇见的功能&#xff0c;比如sql语句中的like,java中的indexof,都是用来判断一个字符串是否包含另外一个字符串的。那么&#xff0c;这些关键字&#xff0c;方法&#xff0c;底层算法是怎么实现的么&#xff1f;本节&#xff0c;我们来探…

字符串匹配算法(BM)

文章目录 1. BM&#xff08;Boyer-Moore&#xff09;算法1.1 坏字符规则1.2 好后缀规则1.3 两种规则如何选择 2. BM算法代码实现2.1 坏字符2.2 好后缀2.3 完整代码2.4 调试 3. 总结 1. BM&#xff08;Boyer-Moore&#xff09;算法 思想&#xff1a;有模式串中不存在的字符&…

六种字符串匹配算法详解(含代码演示)

1. Brute-Force算法 2. Rabin-Karp Hash算法 3. Kmp算法 4. Kmp的优化算法 5. Sunday算法 6. Shift-And算法 ps&#xff1a;字符串匹配其实是单模匹配问题 1.Brute-Force 朴素匹配算法&#xff08;暴力匹配&#xff09; 时间复杂度&#xff1a;O(n*m) //返回 文本串s中第一…

字符串匹配算法(BF、KMP)

BF算法 描述&#xff1a; BF&#xff0c;Brute Force&#xff0c;暴力匹配的意思&#xff0c;是最简单直观的字符串匹配算法。假设有主串s1和子串s2&#xff0c;根据BF算法判断s1是否包含s2的步骤如下&#xff1a; 初始下标指针 i, j 分别指向s1, s2的首位置&#xff0c;若s1…

这可能是全网最好的字符串匹配算法讲解

点击上方 好好学java &#xff0c;选择 星标 公众号重磅资讯&#xff0c;干货&#xff0c;第一时间送达 今日推荐&#xff1a;14 个 github 项目&#xff01;个人原创100W 访问量博客&#xff1a;点击前往&#xff0c;查看更多为保证代码严谨性&#xff0c;文中所有代码均在 le…

Spring boot 项目(五)——AOP切面

一、AOP简介 1、在软件业&#xff0c;AOP为Aspect Oriented Programming的缩写&#xff0c;意为&#xff1a;面向切面编程&#xff0c;通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 2、AOP是OOP的延续&#xff0c;是软件开发中的一个热点&#xff0c;也…

Spring AOP 切面(Aspect)应用详解

1. AOP 切面应用 下面是一个AOP切面的一个简单的应用实例 引入AOP依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>创建切面类对象 Aspect Component pub…

springboot实现AOP切面编程

概述 AOP(Aspect Oriented Programming) 即面向切面编程。面向切面是面向对象中的一种方式而已。在代码执行过程中&#xff0c;动态嵌入其他代码&#xff0c;叫做面向切面编程&#xff08;将交叉业务逻辑封装成成切面&#xff0c;利用AOP功能将切面织入到主业务逻辑———与主…

spring AOP切面及日志记录实现

目录 1.什么是AOP切面 2.理解AOP 3.AOP实例 1.自定义注解 2.创建一个切面类 3.将自定义注解标注在测试接口上 1.什么是AOP切面 AOP&#xff08;Aspect Oriented Programming&#xff09;&#xff0c;面向切面思想&#xff0c;是Spring的三大核心思想之一。 在项目中经常…

Aop切面自定义注解的使用

一&#xff1a;功能简介 本文主要记录如何使用aop切面的方式来实现日志记录功能。 主要记录的信息有: 操作人&#xff0c;方法名&#xff0c;参数&#xff0c;运行时间&#xff0c;操作类型(增删改查)&#xff0c;详细描述&#xff0c;返回值。 二&#xff1a;项目结构图 三…

AOP切面注解

一.前言 在以前的项目中&#xff0c;很少去关注spring aop的具体实现与理论&#xff0c;只是简单了解了一下什么是aop具体怎么用&#xff0c;看到了一篇博文写得还不错&#xff0c;就转载来学习一下&#xff0c;博文地址&#xff1a;http://www.cnblogs.com/xrq730/p/4919025.h…

AOP切面执行顺序

文章目录 一. 概述二. 讲述1. 单切面中各通知方法的执行顺序2. 多切面中各通知方法的执行顺序3. 多切面的通知方法中抛出异常 参考资料 一. 概述 本文主要讲述以下几点 单AOP切面时&#xff0c;各通知方法的执行顺序。多AOP切面时&#xff0c;多切面的执行顺序和各通知方法的执…

spring aop切面执行顺序

spring aop切面执行顺序 切面执行顺序 现有切面1、切面2对同一个切点按先后顺序执行&#xff08;切面1先于切面2执行&#xff09; 切面1&#xff1a;Before、After、AfterReturnning、AfterThrowing、Around 执行前、Around 正常执行、Around 异常执行、Around 执行后切面2&am…