前言

算法是什么?算法就是数学规律.怎么去总结和发现这个规律,就是理解算法的过程.

KMP算法的本质是穷举法,而并不是去创造新的匹配逻辑.

以下将搜寻的字符串称为子串(part),以P表示.被搜寻的字符串称为总串(total),以T表示.

start代表P串在T串中开始匹配的位置,end代表P串与T串对比字符时的位置

        String total = "ababcd";
String part = "abc";
total.contains(part);

部分匹配表

部分匹配表是KMP算法的核心。只要理解了部分匹配表,就基本理解了KMP算法。

普通匹配模式

对比开始.

start=0,end=0;比较T.charAt(0)==P.charAt(0).均为a,此时end右移一位.

start=0,end=1;比较T.charAt(1)==P.charAt(1).均为b,此时end右移一位.

start=0,end=2;比较T.charAt(2)!=P.charAt(2).此时,start右移一位.

start=1,end=0;比较T.charAt(start+end)!=P.charAt(end).此时,start右移一位.

start=2,end=0;比较T.charAt(start+end)==P.charAt(end).此时,此时end右移一位.以此类推.

最终发现T.contains(P)为true,T.indexOf(P)为start,即为2.

    public static int getIndex(String total, String part) {
char[] totalChars = total.toCharArray();
char[] partChars = part.toCharArray();
int start = 0;
int end = 0; while (start < total.length()) {
if (totalChars[start + end] == partChars[end]) {
end++;
} else {
start = start + 1;
end = 0;
}
if (end == part.length()) {
return start;
}
}
return -1;
}

寻找规律

规律是什么?就是在匹配过程中,遇到某一位不匹配时.start与end下一次的起点位置选择.

对于普通匹配而言,start的变化永远是右移一位,end永远是从0开始,并且每次右移一位.

这里先介绍两个规律,当发现end位不匹配时

第一条规律,,新起点位置只与重合部分有关

T.charAt(start+end)!=P.charAt(end)

T.substring(start,start+end)==P.substring(0,end)

因为它不相等,所以它相等,这句话前后顺序不能颠倒.这虽然很像废话,但是确实KMP算法的核心.

第二条规律,未知无法跳过

这次的end位一定是下次比较的起点

这里有个特殊的地方,就是首位不同时的逻辑,代码中也是一样,先按下不表.

部分匹配表

有个比较关键的地方,确定start与end新起点的规则是什么?


新的起点是什么,是可能性,是T串的某一段与P串完全相同的可能性.

只有end=0时相同,才会有end=1时的比较

只有end=1时相同,才会有end=2时的比较

...

那么至少,T.charAt(start)==P.charAt(0),才可以进行后面的比较


当遇到end位不匹配时,我们将start可能移动的轨迹分为两部分

① (start,start+end)

② [start+end,...]

T.indexOf(P)的位置只可能出现在这两个区域(因为之前的位置都被排除了).这两个区域的差别是什么呢?

结合上面两条规律,途经区域①的比较的字符对象是完全已知的,而区域②则不是.

即下一次start的起点在 (start,start+end] 中

因为即要么在(start,start+end)中,要么就是end,因为end是未知的,必须要用首位去对比,所以start最远会位移到end位

由于第一条规律,T.substring(start,start+end)==P.substring(0,end)

那么start在T.substring(start,start+end)中位移的过程就是start在P.substring(0,end)中位移的过程

去寻找start在(start,start+end)中作为新起点的可能性,就是寻P.substring(0,end)这个字符串本身与其子串的重合度,什么是重合度?

两个相同的字符串,一个不动,一个整体向右移动一格,查看两者相交部分,如果相交部分完全相等,那么相交字符串的首位,就是新的起点,这个相交部分的长度就是重合度.


假设 P = abcde

不匹配时end位置 P.substring(0,end) 重合度
0 "" 0
1 a 0
2 ab 0
3 abc 0
4 abcd 0

获取重合度

    public static int getPublicPart(String part) {
int start = 1;
int end = 0;
char[] chars = part.toCharArray();
while (start < part.length()) {
if (chars[start + end] == chars[end]) {
if (end + start == part.length() - 1) {
return part.substring(start).length();
}
end++;
} else {
start++;
end = 0;
}
}
return 0;
}

我们将start移动轨迹的研究,变成了P.substring(0,end)的研究,那么假设T串很长,那么end值可能会出现在任意一个地方,并且相同情况会有多次,所以我们只要事先将所有可能的情况列出,以后遇到相同情况就可以直接套用结果.



为什么可以复用呢?因为P.charAt(end)我们一定知道什么,但是T.charAt(start+end)却有很多种可能,因为它只需要与P.charAt(end)不相等


假设 P = aaaab

不匹配时end位置 P.substring(0,end) 重合度 下一次start位置 下一次end位置
4 aaaa 3 start+4-3 3
3 aaa 2 start+3-2 2
2 aa 1 start+2-1 1
1 a 0 start+1-0 0
0 "" 0 start+0-(-1) 0

我们可以发现,start下一次的位置为 start +( end - P.substring(0,end)的重合度). (end - 重合度) 其实就是start需要位移的距离


end下一次的位置为 P.substring(0,end)的重合度

但是由于P.substring(0,0)为空字符串,比较特殊,首位不同时,start是直接右移一位


故令next[0] = -1 , 当 next[end] < 0时,下一次的end位置指向 0

获取next数组

    public static int[] getNext(String part) {
int[] next = new int[part.length()];
int start = 1;
while (start < part.length()) {
next[start] = getPublicPart(part.substring(0, start));
start++;
}
next[0] = -1;
return next;
}

我们将end位不同时,P.substring(0,end)它的子串与自身的重合度,称之为部分匹配表

Tips:



这里有一个很关键的地方,start可以直接从0移动到2吗?不可以,因为KMP无法违背普通匹配,或者说违背匹配的规律,只有start每次右移一位,即P.charAt(0)与T串的每一位开始比较,才能确认这个位置含不含有可能性,而我们next数组的获取就是通过每次右移一位获取到的.

完整代码

KMP算法的本质就是通过穷举end位不匹配时start与end的移动轨迹,来达到复用的效果.

    public static int indexOf(String total, String part) {
char[] totalChars = total.toCharArray();
char[] partChars = part.toCharArray();
int[] next = getNext(part); int start = 0;
int end = 0;
while (start < total.length()) {
if (totalChars[start + end] == partChars[end]) {
end++;
} else {
// 与普通匹配不同的其实就是end位不同时,下一次start与end的位置选择
start = start + end - next[end];
end = Math.max(0, next[end]);
}
if (end == part.length()) {
return start;
}
}
return -1;
}

小小的一篇文章,写了快一个月,每天晚上将思想转化为文字时,总会有新的理解,修修改改了这么长时间,总觉得文字不够干练.人生亦是如此.

萌新也能看懂的KMP算法的更多相关文章

  1. 从Webpack源码探究打包流程,萌新也能看懂~

    简介 上一篇讲述了如何理解tapable这个钩子机制,因为这个是webpack程序的灵魂.虽然钩子机制很灵活,而然却变成了我们读懂webpack道路上的阻碍.每当webpack运行起来的时候,我的心态 ...

  2. 保证你能看懂的KMP字符串匹配算法

    文章转载自一位大牛: 阮一峰原网址http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm ...

  3. hdu1711(终于搞懂了KMP算法了。。)

    题意:给你两个长度分别为n(1 <= N <= 1000000)和m(1 <= M <= 10000)的序列a[]和b[],求b[]序列在a[]序列中出现的首位置.如果没有请输 ...

  4. Floyd-蒟蒻也能看懂的弗洛伊德算法(当然我是蒟蒻)

    今天来讲点图论的知识,来看看最短路径的一个求法(所有的求法我以后会写,也有可能咕咕咕) 你们都说图看着没意思不好看,那今天就来点情景             暑假,_GC准备去一些城市旅游.有些城市之 ...

  5. Floyd算法-傻子也能看懂的弗洛伊德算法(转)

                暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程.          ...

  6. Floyd-傻子也能看懂的弗洛伊德算法(转)

                暑假,小哼准备去一些城市旅游.有些城市之间有公路,有些城市之间则没有,如下图.为了节省经费以及方便计划旅程,小哼希望在出发之前知道任意两个城市之前的最短路程.          ...

  7. 萌新学习图的强连通(Tarjan算法)笔记

    --主要摘自北京大学暑期课<ACM/ICPC竞赛训练> 在有向图G中,如果任意两个不同顶点相互可达,则称该有向图是强连通的: 有向图G的极大强连通子图称为G的强连通分支: Tarjan算法 ...

  8. Vue初识:一个前端萌新的总结

    一.前言 时隔三年,记得第一次写博客还是2015年了,经过这几年的洗礼,我也从一个后端的小萌新变成现在略懂一点点知识的文青.如今对于前端的东东也算有一知半解,个人能力总的来说,也能够独立开发产品级项目 ...

  9. KMP算法的工作流程介绍

    最近又想起了KMP算法,原来一直没搞明白工作原理,现在总算是开点窍了,推荐大家看这篇文章,写的很简单易懂 推荐理由:简单明了,是我看过介绍KMP算法流程的所有文章中,最易懂的一篇(这篇文章仅仅是介绍了 ...

随机推荐

  1. YII学习总结2(命名空间和操作响应)

    YII基础准备1.命名空间<?php /****假设有三个同名的类,输出的值为A,B,C****/ use a\b\c\apple; use d\e\f\apple as bApple; use ...

  2. React报错之无法在未挂载的组件上执行React状态更新

    正文从这开始~ 总览 为了解决"Warning: Can't perform a React state update on an unmounted component" ,可以 ...

  3. 3.联合索引、覆盖索引及最左匹配原则|MySQL索引学习

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 导语 在数据检索的过程中,经常会有多个列的匹配需求,今天介绍下联合索引的使用以及最左匹配原则的案例. 最左匹配原则作用在联 ...

  4. 一文搞懂│XSS攻击、SQL注入、CSRF攻击、DDOS攻击、DNS劫持

    目录 XSS 攻击 SQL 注入 CSRF 攻击 DDOS 攻击 DNS 劫持 XSS 攻击 全称跨站脚本攻击 Cross Site Scripting 为了与重叠样式表 CSS 进行区分,所以换了另 ...

  5. Spring源码 10 IOC refresh方法5

    本文章基于 Spring 5.3.15 Spring IOC 的核心是 AbstractApplicationContext 的 refresh 方法. 其中一共有 13 个主要方法,这里分析第 5 ...

  6. Python自动化之常用模块学习

    自动化常用模块 urllib和request模块学习笔记 '获取页面,UI自动化校验页面展示作用': #-*- coding : utf-8 -*-import urllib.requestimpor ...

  7. iommu系列之---概念解释篇

    本文会对iommu中的一些容易引起疑惑的概念进行阐述,内核版本为4.19. 先上简写: DMAR - DMA remapping DRHD - DMA Remapping Hardware Unit ...

  8. 为什么各大厂自研的内存泄漏检测框架都要参考 LeakCanary?因为它是真强啊!

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  9. C#基础_XML文件读写

    使用C#对XML文件进行操作,包括生成一个XML文档,以及读取XML文档中的内容,修改某个元素中的内容 using System; using System.Collections.Generic; ...

  10. rh358 001 Linux网络与systemd设置

    358 rhel7 ce ansible 部署服务 dhcp nginx vanish haproxy 打印机服务 服务管理自动化 systemd与systemctl systemctl 来管理sys ...