简介

KMP算法由 Knuth-Morris-Pratt 三位科学家提出,可用于在一个 文本串 中寻找某 模式串 存在的位置。

本算法可以有效降低在一个 文本串 中寻找某 模式串 过程的时间复杂度。(如果采取朴素的想法则复杂度是 \(O(MN)\) )

这里朴素的想法指的是枚举 文本串 的起点,然后让 模式串 从第一位开始一个个地检查是否配对,如果不配对则继续枚举起点。

前置知识

真前缀

指字符串左部的任意子串(不包含自身),如 abcde 中的 a,ab,abc,abcd 都是真前缀但 abcde 不是。

真后缀

指字符串右部的任意子串(不包含自身),如 abcde 中的 e,de,cde,bcde 都是真后缀但 abcde 不是。

前缀函数

一个字符串中最长的、相等的真前缀与真后缀的长度, 如AABBAAA对应的前缀函数值是 \(2\) 。

原理

注意:在分析的时候,我们规定字符串的下标从 \(1\) 开始。

开始:

我们记扫描模式串的指针为j,而扫描文本串的指针为i,假设一开始i,j都在起点,然后让它们一直下去直到完全匹配或者失配,比如:

j
ABCD i
ABCDEFG

然后

 j
ABCD i
ABCDEFG

最后在此完成了一次匹配,类似地如果ABCD改为ABCC则在此失配。

   j
ABCD i
ABCDEFG

i,j运作模式如上。



KMP算法就是,当模式串和文本串失配的时候,j指针从真后缀的末尾跳到真前缀的末尾,然后从真前缀后一位开始继续匹配。(从而起到减少配对次数,这便是KMP算法的核心原理)

结合例子解释:

模式串: \(AABBAAA\)

文本串: \(AABBAABBAAA\)

j指针在最后一个A处失配。

      j
AABBAAA
i
AABBAABBAAA

因为此时 以j为尾的前缀 所对应的前缀函数值是 \(2\) ,所以 j指针 跳到这里:

 j
AABBAAA
i
AABBAABBAAA

然后从下一位开始继续配对:

  j
AABBAAA
i
AABBAABBAAA

最后

      j
AABBAAA
i
AABBAABBAAA

可以看出,KMP能够有效减少配对次数。

实现

我们记模式串p文本串s

从上面的模拟中,我们发现需要预处理出一个数组(记之为next[]),它储存模式串中前缀对应的前缀函数\(\pi()\),如对于字符串ABCABC

\(\pi(0)=0\) (因为什么都没有)

\(\pi(1)=0\) (A甚至没有真前缀真后缀

\(\pi(2)=0\) (AB

\(\pi(3)=0\) (ABC

\(\pi(4)=1\) (ABCA

\(\pi(5)=2\) (ABCAB

\(\pi(6)=3\) (ABCABC

同样地,我们发现如果用暴力朴素的想法来统计复杂度是 O(N^2) 不好,于是采用类似于上面的方法,只不过模式串配对的对象是自己罢了。

可以结合代码理解,并注意举例,尝试在纸上模拟这个过程。

for(int i=2,j=0;i<=lenp;i++){
while(j && p[j+1]!=p[i]) j=next_[j]; // 如果j指向元素的下一个元素会和当前配对位置失配,则j跳回去
if(p[j+1]==p[i]) j++; //如果能够配对上,j++
next_[i]=j; //记录当前位置的前缀函数π
}

完整代码:

#include<bits/stdc++.h>
using namespace std; const int N=1e6+5;
char p[N],s[N];
int next_[N]; int main(){
cin>>s+1>>p+1; int lenp=strlen(p+1),lens=strlen(s+1);
// build next array
for(int i=2,j=0;i<=lenp;i++){
while(j && p[j+1]!=p[i]) j=next_[j]; // 如果j指向元素的下一个元素会和当前配对位置失配,则j跳回去
if(p[j+1]==p[i]) j++; //如果能够配对上,j++
next_[i]=j; //记录当前位置的前缀函数π
} for(int i=1,j=0;i<=lens;i++){
while(j && p[j+1]!=s[i]) j=next_[j];
if(p[j+1]==s[i]) j++; // if match
if(j==lenp){
j=next_[j];
cout<<i-lenp+1<<endl;
}
} for(int i=1;i<=lenp;i++) cout<<next_[i]<<' ';
cout<<endl; return 0;
}

复杂度

\(O(N+M)\)

【算法】KMP算法的更多相关文章

  1. 数据结构与算法--KMP算法查找子字符串

    数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章.这篇文章.还有这篇他们写得非常棒.结合他们的解释和自己的理解,完成了本文. 上一节介绍了暴力法查找子字符串,同时也发 ...

  2. 经典算法 KMP算法详解

    内容: 1.问题引入 2.暴力求解方法 3.优化方法 4.KMP算法 1.问题引入 原始问题: 对于一个字符串 str (长度为N)和另一个字符串 match (长度为M),如果 match 是 st ...

  3. 笔记-算法-KMP算法

    笔记-算法-KMP算法 1.      KMP算法 KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的.具体实现就是实现一 ...

  4. 值得花费一周研究的算法 -- KMP算法(indexOf)

    KMP算法是由三个科学家(kmp分别是他们名字的首字母)创造出来的一种字符串匹配算法. 所解决的问题: 求文本字符串text内寻找第一次出现字符串s的下标,若未出现返回-1. 例如 text : &q ...

  5. [C++] [算法] KMP算法

    KMP串匹配算法是一个经典的算法. 传统BF算法是传统的字符串匹配算法.很好理解.叶实现.但时间复杂度太高. 本文将从字符串模式字符串被称为.为了匹配字符串被称为主弦. KMP配时能够少移动从串的位置 ...

  6. 程序员必会算法-KMP算法

    KMP算法是一种优秀的字符串匹配算法,字符串匹配的常规算法是一步一步进行移位和比较操作,直至找到完全相匹配的字符串. 下面通过一个例子,为大家仔细说明KMP算法的使用和思路: 问题: 在字符串“DEA ...

  7. 算法 kmp算法

    kmp算法是改进后的字符匹配算法,它与bf算法的区别是,每次从串与主串匹配失败后,从串与主串匹配的位置不同. 下面具体说下这两种算法的区别: 主串:BABCDABABCDABCED 从串:ABCDAB ...

  8. BF算法 + KMP算法

    准备: 字符串比大小:比的就是字符串里每个字符的ASCII码的大小.(其实这样的比较没有多大的意义,我们关心的是字符串是否相等,即匹配等) 字符串的存储结构:同线性表(顺序存储+链式存储) 顺序存储结 ...

  9. 图解算法——KMP算法

    KMP算法 解决的是包,含问题. Str1中是否包含str2,如果包含,则返回子串开始位置.否则返回-1. 示例1: Str1:abcd123def Str2:123d 暴力法: 从str1的第一个字 ...

  10. 字符串匹配算法——KMP算法

    处理字符串的过程中,难免会遇到字符匹配的问题.常用的字符匹配方法 1. 朴素模式匹配算法(Brute-Force算法) 求子串位置的定位函数Index( S, T, pos). 模式匹配:子串的定位操 ...

随机推荐

  1. 干货!上古神器 sed 教程详解,小白也能看的懂

    目录: 介绍工作原理正则表达式基本语法数字定址和正则定址基本子命令实战练习 介绍 熟悉 Linux 的同学一定知道大名鼎鼎的 Linux 三剑客,它们是 grep.awk.sed,我们今天要聊的主角就 ...

  2. Jmeter接口自动化测试系列之函数使用及扩展

    介绍一下Jmeter自带函数的使用和 函数扩展,来满足测试工作中的各种需求! Jmeter自带函数 点击函数帮助助手图标,弹出函数助手框,可以选择各种各样的函数 举例: _Random 获取随机数,可 ...

  3. hive 时间相关的函数

    yyyy-MM-dd与yyyyMMdd000000转换的三种方法 方法一:date_format(只支持yyyy-MM-dd -> yyyyMMdd000000) select date_for ...

  4. 极光推送的设备唯一性标识 RegistrationID

    极光推送的设备唯一性标识 RegistrationID 极光推送的设备唯一性标识 RegistrationID | 极光博客 https://blog.jiguang.cn/registrationi ...

  5. 小步前进之WCF简介

    WCF 前言 什么是WCF? 契约 合约 前言 在 .NET Framework2.0 以及前版本中,微软发展了 Web Service..NET Remoting 等通信支持. 如果要进行通信,对于 ...

  6. MySQL 高性能优化规范建议

    数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用 MySQL 保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名 ...

  7. 一文带你看遍 JDK9~14 的重要新特性!

    Java9 发布于 2017 年 9 月 21 日 .作为 Java8 之后 3 年半才发布的新版本,Java 9 带 来了很多重大的变化其中最重要的改动是 Java 平台模块系统的引入,其他还有诸如 ...

  8. Spring Boot整合Spring Data JPA

    1.JPA 2.Spring Data JPA 3.导入依赖 4.连接数据库 5.实体类 6.Repository 7.测试 1.JPA JPA是Java Persistence API的简称,中文名 ...

  9. spark SQL (一)初识 ,简介

    一, 简介 Spark SQL是用于结构化数据处理的Spark模块.与基本的Spark RDD API不同,Spark SQL提供的接口为Spark提供了关于数据结构和正在执行的计算的更多信息.在内部 ...

  10. linux环境变量 shell变量 command not found解决方法(转)

    在Ubuntu.centos中有如下几个文件可以设置环境变量1./etc/profile:在登录时,操作系统定制用户环境时使用的第一个文件,此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文 ...