字符串匹配 - 模式预处理:KMP 算法(Knuth-Morris-Pratt)

news2024/9/25 19:24:23
Knuth-Morris-Pratt算法(简称KMP)是最常用的字符串匹配算法之一。

算法简介

如下算法解释主要来源于这里,但是通常很难阅读完全,我推荐你直接进入下一节 图例解释部分

我们来观察一下朴素的字符串匹配算法的操作过程。如下图(a)中所描述,在模式 P = ababaca 和文本 T 的匹配过程中,模板的一个特定位移 s,q = 5 个字符已经匹配成功,但模式 P 的第 6 个字符不能与相应的文本字符匹配。

此时,q 个字符已经匹配成功的信息确定了相应的文本字符,而知道这 q 个文本字符,就使我们能够立即确定某些位移是非法的。例如上图(a)中,我们可以判断位移 s+1 是非法的,因为模式 P 的第一个字符 a 将与模式的第二个字符 b 匹配的文本字符进行匹配,显然是不匹配的。而图(b)中则显示了位移 s’ = s+2 处,使模式 P 的前三个字符和相应的三个文本字符对齐后必定会匹配。KMP 算法的基本思路就是设法利用这些已知信息,不要把 "搜索位置" 移回已经比较过的位置,而是继续把它向后面移,这样就提高了匹配效率。

The basic idea behind KMP’s algorithm is: whenever we detect a mismatch (after some matches), we already know some of the characters in the text (since they matched the pattern characters prior to the mismatch). We take advantage of this information to avoid matching the characters that we know will anyway match.

已知模式 P[1..q] 与文本 T[s+1..s+q] 匹配,那么满足 P[1..k] = T[s’+1..s’+k] 其中 s’+k = s+q 的最小位移 s’ > s 是多少?这样的位移 s’ 是大于 s 的但未必非法的第一个位移,因为已知 T[s+1..s+q] 。在最好的情况下有 s’ = s+q,因此立刻能排除掉位移 s+1, s+2 .. s+q-1。在任何情况下,对于新的位移 s’,无需把 P 的前 k 个字符与 T 中相应的字符进行比较,因为它们肯定匹配。

可以用模式 P 与其自身进行比较,以预先计算出这些必要的信息。例如上图(c)中所示,由于 T[s’+1..s’+k] 是文本中已经知道的部分,所以它是字符串 Pq 的一个后缀。

此处我们引入模式的前缀函数 π(Pai),π 包含有模式与其自身的位移进行匹配的信息。这些信息可用于避免在朴素的字符串匹配算法中,对无用位移进行测试。

π[q]= max {k : k < q and Pk ⊐ Pq}

π[q] 代表当前字符之前的字符串中,最长的共同前缀后缀的长度。(π[q] is the length of the longest prefix of P that is a proper suffix of Pq.)

下图给出了关于模式 P = ababababca 的完整前缀函数 π,可称为部分匹配表(Partial Match Table)。

计算过程:

  • π[1] = 0,a 仅一个字符,前缀和后缀为空集,共有元素最大长度为 0;

  • π[2] = 0,ab 的前缀 a,后缀 b,不匹配,共有元素最大长度为 0;

  • π[3] = 1,aba,前缀 a ab,后缀 ba a,共有元素最大长度为 1;

  • π[4] = 2,abab,前缀 a ab aba,后缀 bab ab b,共有元素最大长度为 2;

  • π[5] = 3,ababa,前缀 a ab aba abab,后缀 baba aba ba a,共有元素最大长度为 3;

  • π[6] = 4,ababab,前缀 a ab aba abab ababa,后缀 babab abab bab ab b,共有元素最大长度为 4;

  • π[7] = 5,abababa,前缀 a ab aba abab ababa ababab,后缀 bababa ababa baba aba ba a,共有元素最大长度为 5;

  • π[8] = 6,abababab,前缀 .. ababab ..,后缀 .. ababab ..,共有元素最大长度为 6;

  • π[9] = 0,ababababc,前缀和后缀不匹配,共有元素最大长度为 0;

  • π[10] = 1,ababababca,前缀 .. a ..,后缀 .. a ..,共有元素最大长度为 1;

KMP 算法 KMP-MATCHER 中通过调用 COMPUTE-PREFIX-FUNCTION 函数来计算部分匹配表。

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 - m
            q ← π[q]           //Look for the next match.
COMPUTE-PREFIX-FUNCTION(P)
m ← length[P]
π[1] ← 0
k ← 0
for q ← 2 to m
     do 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 π

预处理过程 COMPUTE-PREFIX-FUNCTION 的运行时间为 Θ(m),KMP-MATCHER 的匹配时间为 Θ(n)。

相比较于 NAIVE-STRING-MATCHER,KMP-MATCHER 的主要优化点就是在当确定字符不匹配时对于 pattern 的位移。

NAIVE-STRING-MATCHER 的位移效果是:文本向后移一位,模式从头开始。

    s = s - j +1;
    j =0;

KMP-MATCHER 首先对模式做了获取共同前缀后缀最大长度的预处理操作,位移过程是先将模式向后移 partial_match_length - table[partial_match_length - 1],然后再判断是否匹配。这样通过对已匹配字符串的已知信息的利用,可以有效节省比较数量。

if(j !=0)
        j = lps[j -1];else
        s++;

下面描述了当发现字符 j 与 c 不匹配时的位移效果。

// partial_match_length - table[partial_match_length - 1]
    rrababababjjjjjiiooorababababcauuu
      ||||||||-
      ababababca
    // 8-6=2
    rrababababjjjjjiiooorababababcauuu
      xx||||||-
        ababababca
    // 6-4=2
    rrababababjjjjjiiooorababababcauuu
        xx||||-
          ababababca
    // 4-2=2
    rrababababjjjjjiiooorababababcauuu
          xx||-
            ababababca
    // 2-0=2
    rrababababjjjjjiiooorababababcauuu
            xx-
              ababababca

综上可知,KMP 算法的主要特点是:

  • 需要对模式字符串做预处理;

  • 预处理阶段需要额外的 O(m) 空间和复杂度;

  • 匹配阶段与字符集的大小无关;

  • 匹配阶段至多执行 2n - 1 次字符比较;

  • 对模式中字符的比较顺序时从左到右;

算法图例

如下是阮一峰根据 Jake Boxer的文章总结的图例。

下面,我用自己的语言,试图写一篇比较好懂的KMP算法解释。

首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以搜索词后移一位。

因为B与A不匹配,搜索词再往后移。

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

接着比较字符串和搜索词的下一个字符,还是相同。

直到字符串有一个字符,与搜索词对应的字符不相同为止。

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

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

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

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

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

因为空格与A不匹配,继续后移一位。

逐位比较,直到发现C与D不匹配。于是,移动位数 = 6 - 2,继续将搜索词向后移动4位。

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

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例,

  - "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。

"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。

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

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

相关文章

Linux调试器gdb

本文已收录至《Linux知识与编程》专栏&#xff01; 作者&#xff1a;ARMCSKGT 演示环境&#xff1a;CentOS 7 ​ 目录 前言 正文 下载gdb 生成可调式文件 进入gdb gdb常用指令 查看代码 l 运行程序 r 断点设置 b 显示信息 info 查看断点 info b 删除断点 d …

CentOS7突然没法上网【Network 中wired 图标消失】

参考文章(七种办法)&#xff1a;CentOS 7 右上角网络连接图标消失,设置网络有线消失解决办法 正常图标消失&#xff0c;先在 终端命令 依次执行以下命令 service NetworkManager stop service network restart service NetworkManager start 一、问题真烦 CentOS7图形化界面安装…

Day893.MySQL 实例健康状态检测方法 -MySQL实战

MySQL 实例健康状态检测方法 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于MySQL 实例健康状态检测方法的内容。 在一主一备的双 M 架构里&#xff0c;主备切换只需要把客户端流量切到备库&#xff1b;而在一主多从架构里&#xff0c;主备切换除了要把客户端流量切…

搭建企业级docker仓库—Harbor

一、简介 docker 官方提供的私有仓库 registry&#xff0c;用起来虽然简单 &#xff0c;但在管理的功能上存在不足。 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器&#xff0c;harbor使用的是官方的docker registry(v2命名是distribution)服务去完成。harbor在…

String是如何保证不变的?反射为什么可以改变String的值?

String是如何保证不变的&#xff1f;反射为什么可以改变String的值&#xff1f; 1. String字符串的源码分析 String 字符串到底能不能改变已经是老生常谈的问题了&#xff0c;但是在面试环节中&#xff0c;依然能够难住不少人。 下面我们根据 JDK1.8 版本下的String源码进行…

微信Hook逆向-获取登录二维码

文章目录前言一、打开Pc微信&#xff0c;切换到二维码界面二、解析当前二维码内容三、利用Cheat Enginer软件扫描二维码解析文本四、寻找静态偏移五.代码获取二维码网址前言 微信二维码可以Hook获取,也可以通过找到静态偏移的方式读取 提示&#xff1a;以下是本篇文章正文内容…

力扣(LeetCode)240. 搜索二维矩阵 II(C++)

题目描述 枚举 枚举整个矩阵&#xff0c;找到等于 target 的元素&#xff0c;则 return true &#xff0c;否则 return false。 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int n matrix.size(), m matrix[0]…

DataWhale 大数据处理技术组队学习task2

三、Hadoop分布式文件系统 1. 产生背景 数据量越来越大&#xff0c;一台独立的计算机已经无法存储所有的数据---->将大规模的数据存储到成百上千的计算机中------为了解决数据管理以及维护极其繁琐与低效------>分布式文件系统 分布式文件系统是管理网络中跨多台计算机…

基于SSM框架的狼途汽车门店管理系统的设计与实现

基于SSM框架的狼途汽车门店管理系统的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、…

AutoCAD学习之基本操作学习笔记

AutoCAD学习 基本操作&#xff08;23.2.15~23.2.17&#xff09; CtrlN 新建一个CAD文档F7 删除格栅F3 对象捕捉&#xff08;很重要啊&#xff0c;如果一直开着&#xff0c;操作起来很费劲。&#xff09;&#xff0c;需要关掉&#xff0c;注意使用snipaste&#xff0c;会不停地…

QT 文件监视系统QFileSystemWatcher监视目录的改变directoryChanged和监视文件的改变fileChanged

QT 文件监视系统QFileSystemWatcher监视目录的改变相关操作说明mainwindow.hmainwindow.cpp调试结果相关操作说明 添加头文件 Header: #include qmake: QT core bool QFileSystemWatcher::addPath(const QString &path)如果路径存在&#xff0c;则会向文件系统监视器添…

Prometheus Docker安装及监控自身

前提环境&#xff1a; Docker环境 涉及参考文档&#xff1a; 安装Prometheus开始 Prometheusnode_exporter Agent组件 一、部署Prometheus 1、启动容器将文件拷贝出来 docker run -d prom/prometheus2、容器将文件拷贝出来 docker cp 容器ID:/usr/share/prometheus/conso…

深度学习笔记:误差反向传播(1)

1 计算图 计算图使用图&#xff08;由节点和边构成的图&#xff09;来表达算式。 如图&#xff0c;我们用节点代表运算符号&#xff0c;用边代表传入的参数&#xff0c;即可算出购买苹果和橘子的总价格。 2 计算图的局部计算 局部计算意味着每个节点只处理和其相关的运算&…

网页设计html期末大作业

网页设计html期末大作业网页设计期末大作业-自制网站大一期末作业&#xff0c;外卖网站设计网页设计期末大作业-精美商城-首页框架网页设计期末大作业-自制网站 有导航栏&#xff0c;轮播图&#xff0c;按钮均可点进去&#xff0c;如下图所示 点我下载资源》》》》 大一期末…

linux ubuntu查日志信息以及错误排查

目录 一、linux的日志文件 1、常用日志文件 2、其他日志文件 二、历史日志的查看 1、查看Logrotate的配置信息 2、查看日志配置 一、linux的日志文件 Linux系统中最有趣的(可能也是最重要的)目录之一是/var/log。根据文件系统层次结构标准&#xff0c;在系统中运行的大多数…

java面试题-泛型异常反射

泛型1.什么是泛型&#xff1f;Java是一种强类型语言&#xff0c;数据类型在编译时必须确定。如果我们想要在代码中使用不同类型的数据&#xff0c;那么就需要为每种类型分别写出相应的代码。这样会导致代码冗长、重复&#xff0c;也不便于维护。为了解决这个问题&#xff0c;Ja…

嵌入式Linux入门级板卡的神经网络框架ncnn移植与测试-米尔i.MX6UL开发板

本篇测评由电子发烧友的优秀测评者“ALSET”提供。 米尔 MYD-Y6ULX-V2 开发板&#xff0c;基于 NXP i.MX6UL/i.MX6UL L处理器&#xff0c;该开发板被米尔称之为经典王牌产品。本次测试目标是在此开发板上进行神经网络框架ncnn的移植与测试开发&#xff0c;测试ncnn在此开发板上…

高可用的“异地多活”架构设计

前言 后台服务可以划分为两类&#xff0c;有状态和无状态。高可用对于无状态的应用来说是比较简单的&#xff0c;无状态的应用&#xff0c;只需要通过 F5 或者任何代理的方式就可以很好的解决。后文描述的主要是针对有状态的服务进行分析。 服务端进行状态维护主要是通过磁盘…

Orin 安装CUDA CUDNN TensorRT Opencv和输入法的环境配置

有两种方法可以安装CUDA环境 第一种方法-用命令按照 在刷机完成的Orin&#xff0c;执行如下命令&#xff1a; sudo apt update sudo apt upgrade sudo apt install nvidia-jetpack -y注释–如果在执行第三行命令&#xff0c;报错的话&#xff0c;先查看nvidia-l4t-apt-so…

初识K8s

概览 k8s 概念和架构从零搭建K8s 集群k8s 核心概念搭建集群监控平台搭建高可用k8s集群集群环境 实际部署项目 k8s 概念和架构 1、K8S概述和特性 概述&#xff1a; k8s是谷歌在2014年开源的容器化集群管理系统使用k8s进行容器化应用部署使用k8s利于应用扩展k8s目标实施让部…