【算法】KMP算法
简介
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算法的更多相关文章
- 数据结构与算法--KMP算法查找子字符串
数据结构与算法--KMP算法查找子字符串 部分内容和图片来自这三篇文章: 这篇文章.这篇文章.还有这篇他们写得非常棒.结合他们的解释和自己的理解,完成了本文. 上一节介绍了暴力法查找子字符串,同时也发 ...
- 经典算法 KMP算法详解
内容: 1.问题引入 2.暴力求解方法 3.优化方法 4.KMP算法 1.问题引入 原始问题: 对于一个字符串 str (长度为N)和另一个字符串 match (长度为M),如果 match 是 st ...
- 笔记-算法-KMP算法
笔记-算法-KMP算法 1. KMP算法 KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的.具体实现就是实现一 ...
- 值得花费一周研究的算法 -- KMP算法(indexOf)
KMP算法是由三个科学家(kmp分别是他们名字的首字母)创造出来的一种字符串匹配算法. 所解决的问题: 求文本字符串text内寻找第一次出现字符串s的下标,若未出现返回-1. 例如 text : &q ...
- [C++] [算法] KMP算法
KMP串匹配算法是一个经典的算法. 传统BF算法是传统的字符串匹配算法.很好理解.叶实现.但时间复杂度太高. 本文将从字符串模式字符串被称为.为了匹配字符串被称为主弦. KMP配时能够少移动从串的位置 ...
- 程序员必会算法-KMP算法
KMP算法是一种优秀的字符串匹配算法,字符串匹配的常规算法是一步一步进行移位和比较操作,直至找到完全相匹配的字符串. 下面通过一个例子,为大家仔细说明KMP算法的使用和思路: 问题: 在字符串“DEA ...
- 算法 kmp算法
kmp算法是改进后的字符匹配算法,它与bf算法的区别是,每次从串与主串匹配失败后,从串与主串匹配的位置不同. 下面具体说下这两种算法的区别: 主串:BABCDABABCDABCED 从串:ABCDAB ...
- BF算法 + KMP算法
准备: 字符串比大小:比的就是字符串里每个字符的ASCII码的大小.(其实这样的比较没有多大的意义,我们关心的是字符串是否相等,即匹配等) 字符串的存储结构:同线性表(顺序存储+链式存储) 顺序存储结 ...
- 图解算法——KMP算法
KMP算法 解决的是包,含问题. Str1中是否包含str2,如果包含,则返回子串开始位置.否则返回-1. 示例1: Str1:abcd123def Str2:123d 暴力法: 从str1的第一个字 ...
- 字符串匹配算法——KMP算法
处理字符串的过程中,难免会遇到字符匹配的问题.常用的字符匹配方法 1. 朴素模式匹配算法(Brute-Force算法) 求子串位置的定位函数Index( S, T, pos). 模式匹配:子串的定位操 ...
随机推荐
- 基于.NET Core的优秀开源项目合集
开源项目非常适合入门,并且可以作为体系结构参考的好资源, GitHub中有几个开源的.NET Core项目,这些项目将帮助您使用不同类型的体系结构和编码模式来深入学习 .NET Core技术, 本文列 ...
- 浅谈JavaScript代码性能优化2
一.减少判断层级 从下图代码中可以明显看出,同样的效果判断层级的减少可以优化性能 二.减少作用域链查找层级 简单解释下,下图中第一个运行foo函数,bar函数内打印name,bar作用域内没有name ...
- JAVA获取当前文件路径this.getClass().getResource方法详细讲解
public class Test { public void run() { // TODO Auto-generated method stub System.out.println(" ...
- Spring Boot(IDEA,Gradle)超详细用户管理项目(一)——Hello World
1.构建工具的配置(Gradle):自定义-所有设置:构建.执行.部署-构建工具-Gradle: 设置Gradle用户主目录:(该目录相当于仓库,gradle将下载所需依赖到此目录下),此目录下可新建 ...
- 数据库 | 001-MySQL梳理系列(一)
MySQL基本组成 SQL执行流程 Server 层主要包括连接器.查询缓存.分析器.优化器.执行器,包含了MySQL主要的很多核心功能,以及所有的内置函数.存储过程.触发器.视图等,其实就是所有跨存 ...
- 炸裂!MySQL 82 张图带你飞
之前两篇文章带你了解了 MySQL 的基础语法和 MySQL 的进阶内容,那么这篇文章我们来了解一下 MySQL 中的高级内容. 其他文章: 138 张图带你 MySQL 入门 47 张图带你 MyS ...
- 2021年官网下载各个版本JDK最全版与官网查阅方法
版本说明 1.安装部署JDK (1)环境 (2)官网下载JDK 由于官网的地址会随着时间的修改而更改修改下载地址,现在讲述下通用的界面操作下载JDK,以后JDK收费更严重,估计就只能下载开源的了. A ...
- 【Azure Developer】在Azure Resource Graph Explorer中查看当前订阅下的所有资源信息列表并导出(如VM的名称,IP地址内网/公网,OS,区域等)
问题描述 通过Azure的Resource Graph Explorer(https://portal.azure.cn/#blade/HubsExtension/ArgQueryBlade),可以查 ...
- Map转换为格式化的YAML字符串
yaml与java对象的互转 yaml与java对象的互转有snakeyaml <dependency> <groupId>org.yaml</groupId> & ...
- JVM笔记 -- Java跨平台和JVM跨语言
学习JVM的重要性 从上层应用程序到底层操作系统,到底有哪些东西? 平时开发的应用程序主要基于各种框架,譬如Spring,SpringMVC,Mybatis,而各种框架又是基于Java API来实现的 ...