字符串匹配算法之KMP算法(图例详解)

article/2025/9/21 12:05:05

字符串匹配算法之KMP算法(图例详解)

  • 1.字符串匹配算法及暴力算法
    • 1.1 简介
    • 1.2 示例题目
  • 2.KMP算法(Knuth-Morris-Pratt algorith)
    • 2.1 朴素算法的缺点
    • 2.2 KMP算法
      • 2.2.1 KMP算法中的前缀算法
        • 2.2.1.1 前缀函数pi的定义
        • 2.2.1.2 前缀函数pi的例子
        • 2.2.1.3 前缀函数的代码
      • 2.2.2 KMP算法
        • 2.2.2.1 KMP算法实例
        • 2.2.2.2 KMP算法的代码

1.字符串匹配算法及暴力算法

在字符串匹配算法之暴力做法(朴素算法)我这篇文章已经详细介绍了字符串匹配算法以及它的暴力算法。现在简单复习一下。

1.1 简介

字符串匹配算法又称模式匹配(pattern matching)。该问题可以概括为「给定字符串ST,在主串S中寻找子串T」。字符T称为模式串 (pattern)。

1.2 示例题目

还是使用来自leetcode 28. 实现 strStr()的这道题。

实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
示例 1:

输入: haystack = "hello", needle = "ll"
输出: 2

2.KMP算法(Knuth-Morris-Pratt algorith)

2.1 朴素算法的缺点

在介绍KMP算法之前,我们先回顾一下朴素算法的缺点,有助于我们更好地理解KMP算法。
先看一下这个例子:

txt[] = “AAAAAAAAAAAAAAAAAB”
pat[] = “AAAAB”

如果是朴素算法一个一个对比的话,pat[]一个一个地右移。

第一步:
image.png
第二步:
image.png
第三步:
image.png\

而其实我们在第一步时就已经匹配过中间的3个A了。

image.png
这就是朴素算法重复的部分,而KMP算法就将重复的部分跳过了。

2.2 KMP算法

KMP算法是如何跳过这一部分的,我们首先需要了解前缀函数。

2.2.1 KMP算法中的前缀算法

2.2.1.1 前缀函数pi的定义

给定一个长度为n的字符串s,其 前缀函数 被定义为一个长度为n的数组p[]。 其中p[i] 的定义是:

  1. 如果子串s[0...i]有一对相等的真前缀与真后缀:s[0...k-1]s[i-(k-1)...i],那么p[i]就是这个相等的真前缀(或者真后缀,因为它们相等子串的长度,也就是p[i] = k
  2. 如果不止有一对相等的,那么p[i]就是其中最长的那一对的长度;
  3. 如果没有相等的,那么s[i]=0

简单来说p[i]就是子串s[0...i]最长的相等的真前缀与真后缀的长度。

用数学语言描述如下:
p [ i ] = m a x k = 0... i { k : s [ 0... k − 1 ] = s [ i − ( k − 1 ) ] . . . i } p[i] = max_{k=0...i}\{k:s[0...k-1] = s[i-(k-1)]...i\} p[i]=maxk=0...i{k:s[0...k1]=s[i(k1)]...i}
特别地,规定p[0]=0

2.2.1.2 前缀函数pi的例子

image.png
前缀函数求的也就是图中的“部分匹配值”,而"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。
以图中的"ABCDABD"为例,
1.首先需要找出ABCDABD这一串字符串的所有前缀

  • A
  • AB
  • ABC
  • ABCD
  • ABCDA
  • ABCDAB
  • ABCDABD
    2.然后找出每个前缀字符的最长公共前后缀
  • "A"的前缀和后缀都为空集,共有元素的长度为0
  • "AB"的前缀为[A],后缀为[B],共有元素的长度为0
  • "ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0
  • "ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0
  • “ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A”,长度为1
  • “ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB”,长度为2
  • "ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0
    3.然后就形成了部分匹配值(prefix table)
    每个前缀字符的最长公共前后缀放在一起就形成了部分匹配表,也就是:
  • 0 0 0 0 1 2 0
    和图里也是一样的。

2.2.1.3 前缀函数的代码

以下是伪代码:

COMPUTE-PREFIX-FUNCTION(P)
m ← length[P]
π[1] ← 0
k ← 0
for q ← 2 to mdo while k > 0 and P[k + 1] ≠ P[q]do k ← π[k]if P[k + 1] = P[q]then k ← k + 1π[q] ← k
return π

然后是C++版本的实现:

// C++ Version
vector<int> prefix_function(string s) {int n = (int)s.length();vector<int> pi(n);for (int i = 1; i < n; i++)for (int j = i; j >= 0; j--)if (s.substr(0, j) == s.substr(i - j + 1, j)) {pi[i] = j;break;}return pi;
}

2.2.2 KMP算法

现在终于可以讲KMP算法了,讲完前缀函数,KMP算法其实以及算完成了70%。

2.2.2.1 KMP算法实例

我们用之前用过的这个例子:

txt[] = “BBC ABCDAB ABCDABCDABDE”
pat[] = “ABCDABD”

按照上面的步骤我们已经学会如何写出部分匹配表
image.png
然后我们开始匹配,前面先跳过一直到有重复的字符串中:

1.首先开始对比,一直到D:
image.png
已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

移动位数 = 已匹配的字符数 - 对应的部分匹配值

因为 6 - 2 等于4,所以将搜索词向后移动4位。

2.移动后如下,将c对比:
image.png
因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2(“AB”),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将搜索词向后移2位。

3.移动后,又将A对比:
image.png
因为空格与A不匹配,继续后移一位。

4.再次移动后,又需要重新对比ABCDABD
image.png
逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

5.再次移动后从CDABD开始继续逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将搜索词向后移动7位,这里就不再重复了。
image.png\

2.2.2.2 KMP算法的代码

理解了实例过后,我们就可以看代码了。
先看伪代码:

KMP-MATCHER(T, P)
n ← length[T]
m ← length[P]
π ← COMPUTE-PREFIX-FUNCTION(P)
q ← 0                          //Number of characters matched.
for i ← 1 to n                 //Scan the text from left to right.do while q > 0 and P[q + 1] ≠ T[i]do q ← π[q]        //Next character does not match.if P[q + 1] = T[i]then q ← q + 1     //Next character matches.if q = m               //Is all of P matched?then print "Pattern occurs with shift" i - mq ← π[q]           //Look for the next match.

C++实现:

// C++ program for implementation of KMP pattern searching
// algorithm
#include <bits/stdc++.h>void computeLPSArray(char* pat, int M, int* lps);// Prints occurrences of txt[] in pat[]
void KMPSearch(char* pat, char* txt)
{int M = strlen(pat);int N = strlen(txt);// create lps[] that will hold the longest prefix suffix// values for patternint lps[M];// Preprocess the pattern (calculate lps[] array)computeLPSArray(pat, M, lps);int i = 0; // index for txt[]int j = 0; // index for pat[]while (i < N) {if (pat[j] == txt[i]) {j++;i++;}if (j == M) {printf("Found pattern at index %d ", i - j);j = lps[j - 1];}// mismatch after j matcheselse if (i < N && pat[j] != txt[i]) {// Do not match lps[0..lps[j-1]] characters,// they will match anywayif (j != 0)j = lps[j - 1];elsei = i + 1;}}
}// Fills lps[] for given patttern pat[0..M-1]
void computeLPSArray(char* pat, int M, int* lps)
{// length of the previous longest prefix suffixint len = 0;lps[0] = 0; // lps[0] is always 0// the loop calculates lps[i] for i = 1 to M-1int i = 1;while (i < M) {if (pat[i] == pat[len]) {len++;lps[i] = len;i++;}else // (pat[i] != pat[len]){// This is tricky. Consider the example.// AAACAAAA and i = 7. The idea is similar// to search step.if (len != 0) {len = lps[len - 1];// Also, note that we do not increment// i here}else // if (len == 0){lps[i] = 0;i++;}}}
}// Driver program to test above function
int main()
{char txt[] = "ABABDABACDABABCABAB";char pat[] = "ABABCABAB";KMPSearch(pat, txt);return 0;
}

其他的数据结构与算法的相关内容,我会继续更新在这个专栏,欢迎收藏。


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

相关文章

字符串匹配算法(KMP)

问题&#xff1a; 给你两个字符串 s 和 pat &#xff0c;请你在 s 字符串中找出 pat 字符串出现的第一个位置&#xff08;下标从 0 开始&#xff09;&#xff0c;如果不存在则返回-1。 1.暴力匹配算法。 暴力匹配算法较好理解&#xff0c;其大致原理如图&#xff1a; 当D和E不…

字符串匹配算法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;多切面的执行顺序和各通知方法的执…