4000字长文爆杀KMP
文章目录
- KMP算法
- 最长公共前缀
- 特殊情况
- 利用最长公共前缀加速匹配字符串
KMP算法
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n) [1] 。
最长公共前缀
了解KMP算法之前先要了解一下什么是最长公共前缀
下面有一个字符串
"a b c a b a"
然后这里有一组数组跟上面的字符串对应
[-1 0 0 0 1 2]
跳过前面2个字符串因为前面两个字符串没有公共前缀的概念
because:当字符串长度为2的时候没有取最长公共前缀的意义,我们能够通过2个字符的比较得出结论。
下面来解释一下数组的含义
前面说过了2个字符串没有公共前缀的概率
所以这里我们人为规定数组的第一个值和第二个值分别为-1和0
数组变化
[-1 0]
从字符串下标为2的前面开始观察,当字符串下标为2的时候。
我们从字符串的最前面取1个值a(也就是下标为0的那个元素),从下标为2的前一个2-1也就是下标为1的字符取出来为b
因为a并不等于b,所以我们给字符串下标为2的数字设置为0。
表示字符串下标为2的位置的之前的字符串,存在的最长公共前缀为0
数组变化
[-1 0 0]
这个时候重复前面步骤
从字符串下标为3的前面开始观察,当字符串下标为3的时候。
我们从字符串的最前面取1个值a(也就是下标为0的那个元素),从下标为3的前一个3-1也就是下标为2的字符取出来为c
因为a并不等于c,所以我们给字符串下标为3的数字设置为0。
表示字符串下标为3的位置的之前的字符串,存在的最长公共前缀为0
数组变化
[-1 0 0 0]
字符串还没有观察完继续上述步骤
这个时候我们来到了字符串下标为4的位置
我们从字符串的最前面取1个值a(也就是下标为0的那个元素),从下标为4的前一个4-1也就是下标为3的字符取出来为a
a等于a这个时候终于相等了,所以这个时候给下标为4的的数字设置为1.
表示字符串下标为4的位置的之前的字符串,存在的最长公共前缀为1
数组变化
[-1 0 0 1]
这个时候来到了最后一步了
这个时候我们来到了字符串下标为5的位置
这个时候我们只需要比较字符串的之前的比较过的前面一个值开始比较,因为a和a前面已经比较过了。
所以我们只需要比较字符串下标为1的值和下标为5的前一个也就是5-1的值
因为b=b,所以我们这里只需要取出前一个数组前面一个的值进行加1
数组变化
[-1 0 0 1 2]
特殊情况
这里还有一种特殊情况就是当字符串不相等的情况下这个时候需要跳转比较了。
根据上面的规律这个时候下标为6的a应该和下标为3的值c进行比较
但是a是不等于c的,
所以a这里的指针不动,直接跳转到下标为2的位置的前面的最长公共前缀元素开始比较,取出c位置下标的值,
通过这个索引取出这个下标的字符串和上面指针指向的a开始重新比较。
这里为什么需要跳转我的理解是,因为在指针a的这个位置和c值不相等了,所以公共前缀到这里就已经断了。
所以,需要找到c位置开始之前的公共前缀。在这里需要解释一下字符串下面的数组的值。
这里数组的值可以理解为2种含义,第一个含义就是从这个位置出发前面取出和下标一样个数的公共前缀有几个
第二个含义就是该下标从字符从下标0出发到这个下标的值的范围里面的字符串和第一个含义里面范围的字符串是一样的。
就是上面圈出来的”ab“和”ab“这两个值。所以当a不等于c的时候,从下标数组[2]取出来值等于0
然后取出字符串数组[0]的值开始和指针a开始比较来重新计算一个最长公共前缀。
下面贴一下代码——师承左神
public int[] getKmpArray(char[] needleArray) {int length = needleArray.length;if (length < 2) {return new int[]{-1};}int[] newArray = new int[length];newArray[0] = -1;int a = 2;int b = 0;while (a < length) {if (needleArray[a - 1] == needleArray[b]) {newArray[a++] = ++b;} else if (b > 0) {b = newArray[b];} else {a++;}}return newArray;}
求出这个最长公共前缀之后,我们可以开始干大事了
利用最长公共前缀加速匹配字符串
下面我们来看一下kmp的原理
这里便于观看我先用空格隔开字符串
String haystack="q w e r t r s q w e r t l q w e r t"
String needle="q w e r t r s q w e r t k"
之前的暴力匹配是for循环第一个字符串匹配到最后l的时候,发现和k不匹配,这个时候会重新从w开始遍历开始比较
这个时候时间复杂度就会大大的提高了。
而KMP干了什么一个事,KMP会帮助我们跳转到下面这么一个情况
会直接把我们需要去进行匹配的字符串的l指向r这么一个位置。
这里会有一个疑问?为什么我要指向这个位置?
大家还记得,前面我们求了最长公共前缀这一个数组对吧
仔细观察上面这个字符串我们会发现一个规律
这里的qwert在下面出现了两次,上面出现了一次
我们可以发现qwert和qwert是相等的了。所以我们直接把指针l指向r这个位置让他们来进行比较。
这里的疑问是为什么我能直接把l指向r这个位置?我qwertl前面不会有字符串会跟下面要匹配的字符串能匹配的吗?
这里我们就需要利用反证法了,如果说qwert前面会有字符串跟下面的匹配,那么也就是说会产生一个比qwert更长的
公共前缀,但是我之前已经求出了K前面的最长公共前缀为qwert了它已经是我能找到的最长公共前缀了,所以这里的结论是不成立的。
所以怀疑失败,上面字符串qwertl
之前的字符串我们可以直接放弃不看了。因为根据上面的方我们已经知道了
因为前面部分的字符串是永远也不可能拼出我们要找的字符串的。
根据上面我们所求的数组我们可以得到K这里的值应该为5。所以上面的l应该指向r这个位置。
你可以看成有人直接把下面这个字符串往右边推了一下。
如果r和s不等,那么我们又对r的位置找一个最长公共前缀重复该操作。直到没有公共前缀
也就是当上面的l指向q的时候,已经不能在推了,那么这个时候只有对上面的字符串往下面取值。
换一个开头。
KMP核心代码
/*** KMP算法* @param haystack 目标字符串* @param needle 出现的字符串* @return int 字符串出现的索引(从0开始)*/
public int strStr(String haystack, String needle) {char[] haystackArray = haystack.toCharArray();char[] needleArray = needle.toCharArray();int[] kmpArray = getKmpArray(needleArray);int haystackLength = haystackArray.length;int needleLength = needleArray.length;if (haystack == null || needle == null || needleLength < 1 || haystackLength < needleLength) {return -1;}int haystackIndex = 0;int needleIndex = 0;//保证数组不越界while (haystackIndex < haystackLength && needleIndex < needleLength) {//如果都相同两个数组同时往后面移动并且自增if (haystackArray[haystackIndex] == needleArray[needleIndex]) {haystackIndex++;needleIndex++;//如果不相同有两种情况//如果要比较的数组来到了0的位置,// 因为前面KMP数组规定了数组的第一个值的下标为-1//这里也可以写为needleIndex==0} else if (kmpArray[needleIndex] == -1) {haystackIndex++;} else {//跳转要比较的数组到公共前缀的位置needleIndex = kmpArray[needleIndex];}}//如果指针的位置到了最后一个元素返回-1// 如果没有说明找到了匹配的元素,2个指针指向的位置相减等于要匹配的元素第一次出现的位置return needleIndex == needleArray.length ? haystackIndex - needleIndex : -1;
}
4000字长文爆杀KMP相关推荐
- 2.5w字长文爆肝 C++动态内存与智能指针一篇搞懂!太顶了!!!
动态内存与智能指针 1.动态内存与智能指针 2.shared_ptr类 2.1.make_shared函数 2.2.shared_ptr的拷贝和赋值 2.3.shared_ptr自动销毁所管理的对象 ...
- 3w 字长文爆肝 Java 基础面试题!太顶了!!!
hey guys ,这不是也到了面试季了么,cxuan 又打算重新写一下 Java 相关的面试题,先从基础的开始吧,这些面试题属于基础系列,不包含多线程相关面试题和 JVM 相关面试题,多线程和 JV ...
- 2w 字长文爆肝 JVM 经典面试题!太顶了!
欢迎大家关注我的微信公众号[老周聊架构],Java后端主流技术栈的原理.源码分析.架构以及各种互联网高并发.高性能.高可用的解决方案. 如果你是 Java 中高.高级程序员,那我相信你一定被面试官问过 ...
- 阅读名著《鼠疫》读后感4000字
阅读名著<鼠疫>读后感4000字: <鼠疫>是诺贝尔文学奖获得者,法国作家阿尔贝·加缪在二战时期的一部作品,是一部哲学小说,是一个寓言故事. 故事发生在20世纪40年代的一个法 ...
- python 1 2 3怎么拼接所有可能的数_6000字长文,带你用Python完成 “Excel合并(拆分)” 的各种操作!...
原标题:6000字长文,带你用Python完成 "Excel合并(拆分)" 的各种操作! 一.概述 其实Excel合并这个需求,应该是一个极为普遍的需求了.今天我们就利用Pytho ...
- 【7W字长文】使用LVS+Keepalived实现Nginx高可用,一文搞懂Nginx
往期文章一览 分布式会话与单点登录SSO系统CAS,包含完整示例代码实现 [15W字长文]主从复制高可用Redis集群,完整包含Redis所有知识点 使用LVS+Keepalived实现Nginx高可 ...
- 读《阿里铁军》有感【4000字】
工作后也可能会被要求写读后感哦!这是我为别人整理的4000字非原创读后感,若对你有用,请点赞.关注我哟! 初读<阿里铁军>这本书,吸引我的不仅仅是此书故事性的阿里发展史,更多的是它背后隐含 ...
- 【15W字长文】主从复制高可用Redis集群,完整包含Redis所有知识点
往期文章一览 分布式会话与单点登录SSO系统CAS,包含完整示例代码实现 [7W字长文]使用LVS+Keepalived实现Nginx高可用,一文搞懂Nginx 主从复制高可用Redis集群 分布式架 ...
- 《朗读者》的读后感优秀范文4000字
<朗读者>的读后感优秀范文4000字: 家,简单一个字,能引起无数人的情感共鸣.因为家是每一个人最初的记忆,也是我们最终的归宿.说小了,它是两个人的结合:说大了,它是乡土中国的基座. 乔治 ...
最新文章
- 2020年余丙森概率统计强化笔记-第三章 二维随机变量及其分布- 第四章 数字特征
- 世界地板大会姚红鹏的三问
- 使用animate实现页面过度_很多人都在使用的开源CSS动画效果库——animate.css
- apisix实际应用_Apache APISIX 的高性能实践
- 使用Hadoop自带的例子pi计算圆周率
- Android 开发之 ---- 底层驱动开发(一) 【转】
- 女友晚安之后依然在线:python男友用20行代码写了个小工具
- 25 个 Vue 技巧,开发了 5 年了,才知道还能这么用
- Myeclipse破解后报错解决
- 【Java题解】小米算法面试题
- 在华为手机上查看连接过的wifi密码(不愁记性不好)
- PS批量处理批量裁减不同尺寸图片教程(超详细教程 非常实用)-photoshop
- 4款口碑爆棚的电脑软件,每一款都值得拥有
- 【Linux】Linux关闭防火墙、关机重启和查看系统运行级别
- CMU 15-445/645 PROJECT #1 - BUFFER POOL上(实现线程安全的LRU)
- 2019-详细Android Studio开发百度地图(5)—百度地图_导航和TTS语音播报的实现
- vivo手机互传的文件怎么找到_基于 P2P 的在线文件传输工具,电脑与手机互传文件...
- docker 配置国内镜像源不起作用
- 2023年美国大学生数学建模竞赛(春季赛)
- tcpdump命令帮助和示例