有些算法,适合从它产生的动机,如何设计与解决问题这样正向地去介绍。但KMP算法真的不适合这样去学。最好的办法是先搞清楚它所用的数据结构是什么,再搞清楚怎么用,最后为什么的问题就会有恍然大悟的感觉。我试着从这个思路再介绍一下。大家只需要记住一点,PMT是什么东西。然后自己临时推这个算法也是能推出来的,完全不需要死记硬背。KMP算法的核心,是一个被称为部分匹配表(Partial Match Table)的数组。我觉得理解KMP的最大障碍就是很多人在看了很多关于KMP的文章之后,仍然搞不懂PMT中的值代表了什么意思。这里我们抛开所有的枝枝蔓蔓,先来解释一下这个数据到底是什么。对于字符串“abababca”,它的PMT如下表所示:(PMT主要存储的是target串的部分匹配系数,PMT系数帮助我们快速定位J,避免i回指,同时减小J回指的长度)

就像例子中所示的,如果待匹配的模式字符串有8个字符,那么PMT就会有8个值。

我先解释一下字符串的前缀和后缀。如果字符串A和B,存在A=BS,其中S是任意的非空字符串,那就称B为A的前缀。例如,”Harry”的前缀包括{”H”, ”Ha”, ”Har”, ”Harr”},我们把所有前缀组成的集合,称为字符串的前缀集合。同样可以定义后缀A=SB, 其中S是任意的非空字符串,那就称B为A的后缀,例如,”Potter”的后缀包括{”otter”, ”tter”, ”ter”, ”er”, ”r”},然后把所有后缀组成的集合,称为字符串的后缀集合。要注意的是,字符串本身并不是自己的后缀。

有了这个定义,就可以说明PMT中的值的意义了。PMT中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度

例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。

好了,解释清楚这个表是什么之后,我们再来看如何使用这个表来加速字符串的查找,以及这样用的道理是什么。如图 1.12 所示,要在主字符串"ababababca"中查找模式字符串"abababca"。如果在 j 处字符不匹配,那么由于前边所说的模式字符串 PMT 的性质,主字符串中 i 指针之前的 PMT[j −1] 位就一定与模式字符串的第 0 位至第 PMT[j−1] 位是相同的。这是因为主字符串在 i 位失配,也就意味着主字符串从 i−j 到 i 这一段是与模式字符串的 0 到 j 这一段是完全相同的。而我们上面也解释了,模式字符串从 0 到 j−1 ,在这个例子中就是”ababab”,其前缀集合与后缀集合的交集的最长元素为”abab”, 长度为4。所以就可以断言,主字符串中i指针之前的 4 位一定与模式字符串的第0位至第 4 位是相同的,即长度为 4 的后缀与前缀相同。这样一来,我们就可以将这些字符段的比较省略掉。具体的做法是,保持i指针不动,然后将j指针指向模式字符串的PMT[j −1]位即可。总结:PMT系数帮助我们快速定位J,避免i回指,同时减小J回指的长度。

简言之,以图中的例子来说,在 i 处失配,那么主字符串和模式字符串的前边6位就是相同的。又因为模式字符串的前6位,它的前4位前缀和后4位后缀是相同的,所以我们推知主字符串i之前的4位和模式字符串开头的4位是相同的。就是图中的灰色部分。那这部分就不用再比较了。

有了上面的思路,我们就可以使用PMT加速字符串的查找了。我们看到如果是在 j 位 失配,那么影响 j 指针回溯的位置的其实是第 j −1 位的 PMT 值,所以为了编程的方便, 我们不直接使用PMT数组,而是将PMT数组向后偏移一位。我们把新得到的这个数组称为next数组。下面给出根据next数组进行字符串匹配加速的字符串匹配程序。其中要注意的一个技巧是,在把PMT进行向右偏移时,第0位的值,我们将其设成了-1,这只是为了编程的方便,并没有其他的意义。在本节的例子中,next数组如下表所示。

 public static int KMP(String sourceS, String targetS) {int i=0;int j=0;int next[] =getNext(targetS);//重点领悟循环这段,要会写while(i<sourceS.length() && j<targetS.length()) {if (j==-1 || sourceS.charAt(i) == targetS.charAt(j)) {i++;j++;}else {j= next[j];}}

好了,讲到这里,其实KMP算法的主体就已经讲解完了。你会发现,其实KMP算法的动机是很简单的,解决的方案也很简单。远没有很多教材和算法书里所讲的那么乱七八糟,只要搞明白了PMT的意义,其实整个算法都迎刃而解。

现在,我们再看一下如何编程快速求得next数组。其实,求next数组的过程完全可以看成字符串匹配的过程,即以模式(就是target)字符串为主字符串,以模式字符串(就是target)的前缀为目标字符串,一旦字符串匹配成功,那么当前的next值就是匹配成功的字符串的长度。

具体来说,就是从模式字符串的第一位(注意,不包括第0位)开始对自身进行匹配运算(因为i=0时,pmt系数为0)。 在任一位置,能匹配的最长长度就是当前位置的next值。如下图所示。(这一段可能不太好理解,结合代码来理解)

 public static  int[] getNext(String s) {int next[] = new int[s.length()];int i=0; int j=-1;next[0]=-1;//有越界就改这个地方 s.length()-1//这部分的循环就是字符串匹配while(i<s.length()-1) {if(j==-1 || s.charAt(i)== s.charAt(j)) {next[i+1]=j+1;i++;j++;}else{j=next[j];}}return next;}

整个测试代码:

package Findwork;
/*** @author hadoop* 使用PMT(部分匹配表的方法来)实现KMP算法   可以将暴力求解的时间复杂度O(m*n)降低为O(m+n)**/
public class KMP_PMT {public static void main(String[] args) {
/*      String s1="ababababca";String s2="abababca";*/String s1="qwertyuiopasdfghjklzxcvbnm";String s2="ghjklzxc";int next[]=getNext(s2);for (int i : next) {System.out.print(i+" ");}System.out.println();System.out.println(KMP(s1, s2));}public static int KMP(String sourceS, String targetS) {int i=0;int j=0;int next[] =getNext(targetS);//重点领悟循环这段,要会写while(i<sourceS.length() && j<targetS.length()) {if (j==-1 || sourceS.charAt(i) == targetS.charAt(j)) {i++;j++;}else {j= next[j];}}if (j==targetS.length()) return i-j;elsereturn -1;}public static  int[] getNext(String s) {int next[] = new int[s.length()];//定义大一个,防止越界,只有前length个数据有用int i=0; int j=-1; //求next数组的j 初始化为-1。这样设计是要使next[1]=0;next[0]=-1;//有越界就改这个地方 s.length()-1//这部分的循环就是字符串匹配  循环和上面很相似while(i<s.length()-1) {if(j==-1 || s.charAt(i)== s.charAt(j)) {next[i+1]=j+1;i++;j++;}else{//j=next[j];//一旦不匹配成功。J 一直回退到-1j = -1;//其实就是回退到-1, 检查这样写也是正确的}}return next;}}

输出:

-1 0 0 1 2 3 4 0 
2

KMP算法详解:使用部分匹配表PMT来理解KMP算法,使用Java实现相关推荐

  1. 深度学习 --- BP算法详解(流程图、BP主要功能、BP算法的局限性)

    上一节我们详细推倒了BP算法的来龙去脉,请把原理一定要搞懂,不懂的请好好理解BP算法详解,我们下面就直接把上一节推导出的权值调整公式拿过来,然后给出程序流程图,该流程图是严格按照上一节的权值更新过程写 ...

  2. 点云配准NDT (P2D)算法详解

    点云配准NDT (P2D)算法详解 最近了解了一些关于点云配准算法NDT的相关文章,进行总结一下. NDT算法的关键是其利用正态分布对参考点云进行了重新表示,使用点云在一个模型特定位置的似然值而不是直 ...

  3. 最小生成树-Prim算法详解(含全部代码)

    目录 适用条件 测试所用图 算法详解 Prim算法代码 全部代码 实验结果 适用条件 加权连通图 测试所用图 所用原图及生成过程 其中,(a) 为原图,圆圈里面是节点的名称,边上的数字是边的权值.由实 ...

  4. AnchorFree系列算法详解

    目录 前言 一.Anchor-Based方法回顾 二.Anchor Free系列方法简介 1. Anchor Free系列算法历史 2. Anchor free经典算法详解 2.1. 基于关键点的An ...

  5. 【面试】Raft算法详解

    文章目录 前言 一.Raft算法概述 二.Leader选举 三.日志同步 四.安全性 五.日志压缩 六.成员变更 七.Raft与Multi-Paxos的异同 八.Raft算法总结 参考 前言 Paxo ...

  6. SF图像滤镜/美颜/美妆算法详解与实战

    本专栏将结合本热多年相关经验,从传统算法到火热的AI算法,给大家详细讲解目前在PC图像软件.手机图像处理类应用app,以及视频直播等应用类型中,图像视频的滤镜特效,人像美颜美妆特效的算法理论,并结合具 ...

  7. 神经网络算法详解 01:人工神经网络基础

    本文介绍了人工智能的发展历史,基本概念,应用领域:神经元模型,神经元的学习规则以及神经网络工作原理.本系列文章来自阿里云大学人工智能学习路线中的<神经网络概览及神经网络算法详解>课程. 系 ...

  8. 【分享实录】BANCOR算法详解及代码实现

    1 活动基本信息 1)主题:[区块链技术工坊22期]BANCOR算法详解及代码实现 2)议题: BANCOR算法的特点和优劣势 BANCOR算法和举例 如何加入BANCOR.NETWORK交易所 如何 ...

  9. ls路由算法_路由算法详解

    路由算法详解1. 引言 2. 路由器基础知识 3. LS算法 4. 示例:Dijkstra算法 5. DV算法 6. 分级路由 如果您已经阅读过博闻网中的路由器工作原理一文,您会了解到路由器的作用是管 ...

最新文章

  1. Lisp和前缀表示法入门
  2. Java获得泛型类中T的实例
  3. NET问答: 为什么仅有 getter 的属性,还可以在构造函数中赋值 ?
  4. html列表的三种形式 1128
  5. 单片机单口不可用或被占用_新唐单片机代码评审总结
  6. jQuery File Upload blueimp with struts2 简单试用
  7. 如何用PS把背景完全扣掉,变成透明
  8. 最全的Java多线程面试题
  9. Python安装包及初步认知
  10. linux tomcat startup.sh,Tomcat 安装教程与启动-startup.sh
  11. 如何在 Word 的方框中打勾 √ ?☑ ☒
  12. 智能家居新体验:什么样的数据让语音交互更智慧
  13. Zookeeper介绍、原理及应用
  14. 年轻人猝死频发,AI应用到心血管疾患筛查还有多远?
  15. 【FPGA】时钟信号几种设计方法
  16. 专访超脑链团队:公链比联盟链更适合赋能传统企业
  17. Share:电脑右下角 今日热文 广告和图标怎么关闭
  18. uniapp中App升级和wgt热更新的逻辑处理
  19. matlab 卷积求图像梯度,图像的卷积(滤波)运算(一)——图像梯度
  20. Sublime Text 3中的OpenSees插件配置

热门文章

  1. python—让繁琐工作自动化
  2. 软购联盟让正版软件得到更好的推广
  3. 农村中学计算机教室管理制度,中小学专用教室管理标准.doc
  4. vuejs集成simditor
  5. python 学习 字典
  6. 22春天津大学《钢结构设计原理》在线作业1
  7. Android 怎么通过adb 查看ip信息
  8. 用计算机的坏处300字作文,网络危害作文300字.docx
  9. 第一次链接出租房的宽带弹出上网公告
  10. 系分 - 数学与经济管理