算法笔记之KMP算法
本文是《算法笔记》KMP算法章节的阅读笔记,文中主要内容来源于《算法笔记》。本文主要介绍了next数组、KMP算法及其应用以及对KMP算法的优化。
KMP算法主要用于解决字符串的匹配问题。即给定两个字符串text与pattern,需要判断pattern是否是text的子串。假设text的长度为n,pattern的长度为m,那么用暴力搜索的算法解决该问题需要的时间复杂度为O(m*n)。这种算法在m,n大于105级别是无法被接受。而KMP算法需要的时间复杂度仅为O(m+n)。Knuth、Morris、Pratt是发明这个算法的三位科学家。
1.next 数组
1.1 next数组定义
在学习KMP算法之前首先学习一下next数组。假设有一个字符串(下标从0开始)。next[i] 表示使该字符串的子串s[0···i]的前缀s[0···k]和后缀s[i-k···i]相等的最大k(k<i:前后缀可以重合但不能为整个子串);如果找不到相等的前后缀组,那么next[i] = -1。
以字符串s = "ababaab"为例,其对应的next数组为[-1,-1,0,1,2,3,-1]。
当i = 0: 子串为"a",找不到相等的前后缀(因为前后缀不能包含整个s[i]),因此next[0] = -1;
当i = 1: 子串为"ab",找不到相等的前后缀,因此next = -1;
当i = 2: 子串为"aba", k = 0时 "a" = "a"; k = 1时,"ab" != "ba";因此next[2] = 0;
依次类推可得到整个next数组。
1.2 next数组的计算
暴力求解next数组效率不高,下面介绍如何用“递推”的方式高效的求解next数组。即假设已经求出next[0]~next[i-1],使用它们的结果来快速得到next[i]。
为方便讨论,假设已知next[0] = -1 ; next[1] = -1 ; next[2] = 0; next[3] = 1,现在来求解next[4]。当得到next[3] = 1时,最长相等的前后缀为"ab",之后在计算next[4]时,由于s[4] == s[next[3]+1],故可将"ab"拓展为“aba”,即next[4] = next[3] + 1 = 2。
在此基础上计算next[5]。由于s[5] ! = s[next[4]+1],因此不能拓展。此时需要做的操作是缩短。希望能找到一个j,使得s[5] ==s[j+1]成立并满足s[0···j]是s[0···2]的一个后缀。同时为了满足相等的前后缀尽量长的目标,找到的j应该尽可能大。
很显然,只需要令j = next[2](找到"aba"的k),判断其是否满足s[5] ==s[j+1]:如果成立,next[5] = j + 1;如果不成立,就不断让j = next[j],直到j回到-1,或是途中s[5] = = s[j+1]成立。在本例中,j = -1时满足条件,故next[5]=-1+1 = 0。
上述过程总结为一般算法描述为:
- 初始化next数组,令j = next[0] = -1;
- 让i 在1~len-1范围遍历(len为字符串s的长度),对每个i,执行3,4,以求出next[i];
- 不断让j = next[j],直到j退回到-1,或是s[i] == s[j+1]成立;
- 如果s[i] == s[j+1],则next[j] = j+1;否则next[i] = j。
代码实现如下:
//getNext 求解长度为len的字符串s的next数组
void getNext(char s[],int len){
int j = -1;
next[0] = -1; //init
for(int i = 1; i < len ; i++){ //求解next[1] ~ next[len - 1]
while(j != -1 && s[i] != s[j+1] ){
j = next[j];
}
if(s[i] == s[j+1]){
j++; //j指向next[i]
}
next[i] = j; } }
2.KMP算法
2.1 KMP算法实现字符串匹配
设有text = "abababaabc",pattern = "ababaab",希望判断patten是否是text的子串。令i指向text的当前欲比位,令j指向patten中当前已被匹配的最后一位,这样只需要满足text[i]==pattern[j+1],就说明pattern[j+1]也被匹配成功,此时让i,j加1以继续比较;如果不满足text[i]==pattern[j+1],参见第一小节next数组的定义,只需令j = next[j],继续比较即可。在本例中,i指向text[4],j指向pattern[3],此时满足text[i] == pattern[j+1],故i,j加一继续匹配。当i指向text[5],j指向pattern[4]时不满足text[i] == pattern[j+1]匹配失败。令j = next[j] =2,此时满足匹配条件,故i,j均加一继续匹配。随后发现直至j==6均成功匹配,说明pattern是text的一个子串。
下面给出算法描述与代码实现:
- 初始化j = -1,j表示pattern当前已被匹配到的最后位;
- 让i遍历文本串text,对每个i,执行3,4来试图匹配text[i]和pattern[j+1]l
- 不断令j = next[j],直到j回退到-1,或是text[i]=pattern[j+1]成立;
- 如果text[i] == pattern[j+1],则令j++。如果j达到m-1,说明pattern是text的子串,返回true。
1 //KMP算法 判断pattern是否是text的子串
2 bool KMP(char text[],char pattern[]) {
3 int n = strlen(text); m = strlen(pattern);
4 getNext(pattern,m); //计算pattern的next数组
5 int j = -1;
6 for(int i = 0; i < n; i++){
7 while(j != -1 && text[i] != pattern[j+1]){
8
9 j = next[j]; //j回退使得满足条件或回到原点
10 }
11 if(text[i] == pattern[j+1]){
12 j++; //匹配成功,j指向已匹配的最后一位
13
14 }
15 if(j == m-1){
16 return true; //已全部匹配完成
17 }
18
19 }
20 return false; //匹配结束,失败
21
22 }
可以发现,求解next数组的过程其实就是模式串pattern进行自我匹配的过程。故上述代码与求解next数组的代码极为类似。
2.2 KMP算法实现统计出现次数
每次成功匹配统计次数的变量加1,成功匹配之后i = i+1,j 回退使得pattern[j+1] == text[i]。直接贴代码:
1 //KMP算法 统计pattern在text出现的次数
2 bool KMP(char text[],char pattern[]) {
3 int n = strlen(text); m = strlen(pattern);
4 getNext(pattern,m); //计算pattern的next数组
5 int j = -1,ans = 0;
6 for(int i = 0; i < n; i++){
7 while(j != -1 && text[i] != pattern[j+1]){
8
9 j = next[j]; //j回退使得满足条件或回到原点
10 }
11 if(text[i] == pattern[j+1]){
12 j++; //匹配成功,j指向已匹配的最后一位
13
14 }
15 if(j == m-1){
16 ans++;
17 j = next[j];
18 }
19
20 }
21 return ans;
22
23 }
2.3 时间复杂度分析
for语句循环了n次,复杂度为O(n)。在每一个for循环内,j要么自增1,要么减小。由于减小到最低为-1。所以while循环对于整个过程最多循环n次,平均每次是O(1)。因此可认为for语句的时间复杂度为O(n)。考虑到计算next数组需要O(m)的时间复杂度(用同样的分析方法),因此KMP算法总共需要O(m+n)的时间复杂度。
算法笔记之KMP算法的更多相关文章
- 算法起步之kmp算法
[作者Idlear 博客:http://blog.csdn.net/idlear/article/details/19555905] 这估计是算法连载文章的最后几篇了,马上就要 ...
- 算法笔记_071:SPFA算法简单介绍(Java)
目录 1 问题描述 2 解决方案 2.1 具体编码 1 问题描述 何为spfa(Shortest Path Faster Algorithm)算法? spfa算法功能:给定一个加权连通图,选取一个 ...
- 萌新笔记——用KMP算法与Trie字典树实现屏蔽敏感词(UTF-8编码)
前几天写好了字典,又刚好重温了KMP算法,恰逢遇到朋友吐槽最近被和谐的词越来越多了,于是突发奇想,想要自己实现一下敏感词屏蔽. 基本敏感词的屏蔽说起来很简单,只要把字符串中的敏感词替换成"* ...
- 问题 1690: 算法4-7:KMP算法中的模式串移动数组
题目链接:https://www.dotcpp.com/oj/problem1690.html 题目描述 字符串的子串定位称为模式匹配,模式匹配可以有多种方法.简单的算法可以使用两重嵌套循环,时间复杂 ...
- 算法笔记--lca倍增算法
算法笔记 模板: vector<int>g[N]; vector<int>edge[N]; ][N]; int deep[N]; int h[N]; void dfs(int ...
- 字符串匹配(BF算法和KMP算法及改进KMP算法)
#include <stdio.h> #include <string.h> #include <stdlib.h> #include<cstring> ...
- 算法笔记_066:Kruskal算法详解(Java)
目录 1 问题描述 2 解决方案 2.1 构造最小生成树示例 2.2 伪码及时间效率分析 2.3 具体编码(最佳时间效率) 1 问题描述 何为Kruskal算法? 该算法功能:求取加权连通图的最小 ...
- 算法笔记_054:Prim算法(Java)
目录 1 问题描述 2 解决方案 2.1 贪心法 1 问题描述 何为Prim算法? 此处引用网友博客中一段介绍(PS:个人感觉网友的这篇博客对于Prim算法讲解的很清楚,本文与之相区别的地方在于具 ...
- 算法笔记_070:BellmanFord算法简单介绍(Java)
目录 1 问题描述 2 解决方案 2.1 具体编码 1 问题描述 何为BellmanFord算法? BellmanFord算法功能:给定一个加权连通图,选取一个顶点,称为起点,求取起点到其它所有顶 ...
随机推荐
- 使用free掉的内存的危害
1 源码 #include <stdio.h> #include <stdlib.h> // 编译环境 gcc int main(void) { printf("** ...
- 013 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 07 基本数据类型变量的存储
013 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 07 基本数据类型变量的存储 变量和它的值如何在内存中进行存储的? 前面学习过:Java中的数据类型分为基本 ...
- 在uniapp或者vue中单行文字或者符号无法换行的终极解决方案
在VUE开发过程中,会出现比较诡异的情况. 比如常规的英文或中文显示都是很正常的,但是当出现了一些中文符号(比如,!等等)在文末的时候,总是会超出view的显示区域. 那么在遇到上面这种问题我们记得检 ...
- Jmeter JDBC Request 使用详解
本篇博文讲解以MySQL为例,搞懂JDBC Request中MySQL的使用方法,换成其它数据库, 如Oracle.PSQL也会很容易上手. 一.基本配置 1.首先我们先了解一下,不同数据库的驱动类和 ...
- IDEA设置External Tools之Javap反编译字节码
通过Jdk的命令javap可以反编译查看字节码,但是在使用idea的时候一直用命令行去操作不太好操作,而且因为idea会把class码 放在target里面,经常会忘记切换目录.这个时候idea的Ex ...
- VUE 安装项目
注意:在cmd中执行的命令 1,前提是安装了node.js 查看 npm 版本号 2,创建项目路径 mkdir vue cd vue 3,安装vue-cli (脚手架) npm install -个v ...
- ConcurrentHashMap原理分析(二)-扩容
概述 在上一篇文章中介绍了ConcurrentHashMap的存储结构,以及put和get方法,那本篇文章就介绍一下其扩容原理.其实说到扩容,无非就是新建一个数组,然后把旧的数组中的数据拷贝到新的数组 ...
- 【C语言程序设计】小游戏之俄罗斯方块(二)!适合初学者上手、练手!
第二篇,主要实现俄罗斯方块中的主体部分,包括容器的数据结构以及容器的相关操作,特别是大方块和容器之间的交互逻辑,包括碰撞检测,消除检测等等. 1. 容器的表示 大方块的实现涉及到位运算,而容器同样如此 ...
- spring boot:使用poi导出excel电子表格文件(spring boot 2.3.1)
一,什么是poi? 1,poi poi是用来兼容微软文档格式的java api, 它是apache的顶级项目之一, 也是我们在生产环境中导出excel时使用最多的库 2,poi官方网站: http:/ ...
- STM32时钟和定时器
时钟源 STM32包含了5个时钟源,分别为HSI.HSE.LSI.LSE.PLL. HSI是高速内部时钟.RC振荡器,频率为8MHz: HSE是高速外部时钟,即晶振,可接石英/陶瓷谐振器或接外部时钟源 ...