KMP算法是众多优秀的模式串匹配算法中较早诞生的一个,也是相对最为人所知的一个。

算法实现简单,运行效率高,时间复杂度为O(n+m)(n和m分别为目标串和模式串的长度)

当字符串长度和字符集大小的比值很大时,KMP算法相对蛮力有着很大的优势

理解KMP算法,关键是理解其中的精髓——next[]数组。

(统一起见,下文将目标字符串记作obj,将模式字符串记作pattern,这与后面的程序代码是一致的)

我们给一个字符串S定义一个next值,记作next(S),next(S)=n表示:

(1)S的前n个字符构成的前缀,和后n个字符的后缀是相等的

(2)n是满足(1)条件的极大值。

如果不存在一个满足(1)条件的n,那么记next(S)=0。

例如,字符串“GACC”的next值为0,“GACCGG”的next值为1(极大前(后)缀为“G"),

“GACCGGACC”的next值为4(极大前(后)缀为”GACC“),“GAGAG”的next值为5。(极大前(后)缀就是它本身)。

而next[]数组,记录的则是pattern的所有前缀的next值。

next数组的作用,就是在模式匹配的过程中,尽可能减少模式串的偏移量。

下令obj=”GACGAACGACCGACGACGCCGACGAC“(O__O"…),pattern=”GACGCCG“。

模式匹配的流程如下:

设立两个”游标“i和j,分别指向obj串和pattern串当前正在检查的字符,初始时令i=j=0。

首先检查第一个字符,若obj[i]==pattern[j],那么i++,j++。直到遇到obj[i]!=pattern[j]的情形。

GACGAACGACCGACGACGCCGACGAC

GACGCCG

前4个字符检查通过,但第5个字符(i=j=4)出了问题。

朴素的做法是让i和j都回退,对于此例,回退至i=1,j=0,然而这样无疑会做许多重复的计算。

而KMP算法的做法则是:只移动pattern。而移动的方法,则关系到算法的效率甚至正确性。

我们的原则是:保证正确的前提下,使得重复判断的次数尽可能少。

最佳的做法是将j移动到next[j-1]的位置。

(1)由next的性质(满足“前缀等于后缀”条件的极大值)可知,这样可以省去尽可能多的判断

(2)并且可以保证这样做是正确的

此时j=4,而next[j-1]=next[3]=1,所以令j=1,再次进行比较。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

比对通过。令i=5,j=2。

GACGAACGACCGACGACGCCGACGAC

------GACGCCG

next[1]=0,所以令j=0。

GACGAACGACCGACGACGCCGACGAC

----------GACGCCG

即使回退到第一位也没有出现字符相等的情形。而我们已经无法回退了。

此时只能令i++,表示前面的部分检查无果,继续向后检查。

(你也可以理解成,让obj相对于pattern运动)

GACGAACGACCGACGACGCCGACGAC

--------------GACGCCG

重复上述的过程到了这里(i=10,j=3),next[2]=0

GACGAACGACCGACGACGCCGACGAC

--------------------GACGCCG

pattern无法回退了,所以向前检查。

GACGAACGACCGACGACGCCGACGAC

----------------------GACGCCG

i=15,j=4,next[3]=1

GACGAACGACCGACGACGCCGACGAC

----------------------------GACGCCG

回退之后比对成功,i++,j++,重复这一过程,直到……

GACGAACGACCGACGACGCCGACGAC

----------------------------GACGCCG

至此我们便利用next数组完成了模式串匹配的过程。

可以看到,next数组使得匹配过程中少了很多不必要的计算,整个匹配过程显得高效利落。

那么问题来了,怎样高效地求next数组?暴力是肯定行不通的。

事实上,next数组的求法和上述KMP算法的步骤惊人地相似,甚至可以说,求next数组的过程就是一个自我匹配的过程。

也就是:求next[j],就是让pattern[0..j]和pattern[0..x]进行上述的匹配过程。这里x=next[j-1]。next[j]=能够匹配成功的子串的最大长度。

下令pattern=”GACCGGACCGA“

首先令next[0]=0,易知next[1]=0。

之后,由于pattern[2]!=pattern[0],所以next[2]=0。同理next[3]=0。

由于pattern[4]==pattern[0],所以next[4]=0+1=1。

这里的0是next[3]的值。表示字符pattern[4]可以接到next[3]对应的后缀之后。

由于pattern[5]!=pattern[1],pattern[5]==pattern[next[0]]==pattern[0],所以next[5]=1。

详情如下:

GACCGG

--------GA    【 用pattern[0..1]匹配pattern[0..5],这里的1是next[4] 】

匹配不通过,所以令”模式串“的游标移至next[1]=0处(加了引号是因为这里的”模式串“是对pattern本身进行匹配)

GACCGG

----------GA

类似地,可得:next[6]=next[5]+1=2,next[7]=next[6]+1=3,next[8]=next[7]+1=4,next[9]=next[8]+1=5。

next[10]的求法如下:

GACCGGACCGA

----------GACCGG    【 next[4]=1 】

GACCGGACCGA

------------------GACCGG

故next[10]=2。

代码如下:

 #include <cstdio>
 #include <cstring>

 ;

 char obj[maxL];
 char pattern[maxL];

 void input()
 {
     scanf("%s%s",obj,pattern);
 }

 };

 int getNext()
 {
     next[]=;
     int len=strlen(pattern);
     ;i<len;i++)
     {
         ];
          && pattern[i] != pattern[k]) k=k?next[k-]:-;
         next[i]=k+;
     }
     return len;
 }

 int kmp() //searching for the first place that pattern appears in obj
 {
     int lenObj=strlen(obj);
     int lenPattern=getNext();
     ,j=;i<lenObj;i++)
     {
          && obj[i] != pattern[j]) j=j?next[j-]:-;
         ;
     }
     ; //not found
 }

 int main()
 {
     input();
     printf("%d\n",kmp());
     ;
 }

(我们用-1作为循环终止的条件)

附一道简单的练习题,求pattern在obj中出现的次数。

 #include <cstdio>
 #include <cstring>

 ;

 char obj[maxL];
 char pattern[maxL];

 void input()
 {
     scanf("%s%s",pattern,obj);
 }

 };

 int getNext()
 {
     next[]=;
     int len=strlen(pattern);
     ;i<len;i++)
     {
         ];
          && pattern[i] != pattern[k]) k=k?next[k-]:-;
         next[i]=k+;
     }
     return len;
 }

 int kmp()
 {
     ;
     int lenObj=strlen(obj);
     int lenPattern=getNext();
     ,j=;i<lenObj;i++)
     {
          && obj[i] != pattern[j]) j=j?next[j-]:-;
         ]; }
     }
     return ans;
 }

 int main()
 {
     int T; scanf("%d",&T);
     while(T--)
     {
         input();
         printf("%d\n",kmp());
     }
     ;
 }

Problem:POJ P3461

算法这东西,难者不会,会者不难。如果理解时遇到了瓶颈,不妨用笔实验一下,总结规律,也许就能悟出算法的思想。

ps.推荐另一篇博文:http://blog.csdn.net/OIljt12138/article/details/51107585,可以作为阅读本文的对照参考

浅谈KMP算法及其next[]数组的更多相关文章

  1. 【文文殿下】浅谈KMP算法next数组与循环节的关系

    KMP算法 KMP算法是一种字符串匹配算法,他可以在O(n+m)的时间内求出一个模式串在另一个模式串下出现的次数. KMP算法是利用next数组进行自匹配,然后来进行匹配的. Next数组 Next数 ...

  2. 浅谈KMP算法

    一.介绍 烤馍片KMP算法是用来处理字符串匹配问题的.比如说给你两个字符串A,B,问B是不是A的子串? 比如,eg就是aeggx的子串 一般讲字符串A称为主串,用来匹配的B串称为模式串 定义n为字符串 ...

  3. 【字符串算法3】浅谈KMP算法

    [字符串算法1] 字符串Hash(优雅的暴力) [字符串算法2]Manacher算法 [字符串算法3]KMP算法 这里将讲述  [字符串算法3]KMP算法 Part1 理解KMP的精髓和思想 其实KM ...

  4. 浅谈KMP算法——Chemist

    很久以前就学过KMP,不过一直没有深入理解只是背代码,今天总结一下KMP算法来加深印象. 一.KMP算法介绍 KMP解决的问题:给你两个字符串A和B(|A|=n,|B|=m,n>m),询问一个字 ...

  5. 浅谈 KMP 算法

    最近在复习数据结构,学到了 KMP 算法这一章,似乎又迷糊了,记得第一次学习这个算法时,老师在课堂上讲得唾沫横飞,十分有激情,而我们在下面听得一脸懵比,啥?这是个啥算法?啥玩意?再去看看书,完全听不懂 ...

  6. 单模式串匹配----浅谈kmp算法

    模式串匹配,顾名思义,就是看一个串是否在另一个串中出现,出现了几次,在哪个位置出现: p.s.  模式串是前者,并且,我们称后一个 (也就是被匹配的串)为文本串: 在这篇博客的代码里,s1均为文本串, ...

  7. poj 2406:Power Strings(KMP算法,next[]数组的理解)

    Power Strings Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 30069   Accepted: 12553 D ...

  8. KMP算法的next[]数组通俗解释

    原文:https://blog.csdn.net/yearn520/article/details/6729426 我们在一个母字符串中查找一个子字符串有很多方法.KMP是一种最常见的改进算法,它可以 ...

  9. 浅谈分词算法(5)基于字的分词方法(bi-LSTM)

    目录 前言 目录 循环神经网络 基于LSTM的分词 Embedding 数据预处理 模型 如何添加用户词典 前言 很早便规划的浅谈分词算法,总共分为了五个部分,想聊聊自己在各种场景中使用到的分词方法做 ...

随机推荐

  1. Windows 8 关闭无线后无法打开WIFI的解决办法

    转:http://blog.sina.com.cn/s/blog_49d02ed101016b4n.html 在使用 Windows 8 的过程中,遇到过几次使用键盘上的功能键(笔者笔记本上的快捷键是 ...

  2. vijosP1014 旅行商简化版

    vijosP1014 旅行商简化版 链接:https://vijos.org/p/1014 [思路] 双线DP. 设ab,ab同时走.用d[i][j]表示ab所处结点i.j,且定义i>j,则有转 ...

  3. LCD显示的一些基本概念以及DSI的一些clock解释

     数字视频的基本概念源自于模拟视频.对于模拟视频我们可以这样理解:视频可以分解为若干个基本视点(像素),每个像素都有独立的色彩信息,在屏幕上依次将 这些点用电子枪按照行和列打出来,就形成了一幅完整画面 ...

  4. [zz]android的logcat详细用法

    Android日志系统提供了记录和查看系统调试信息的功能.日志都是从各种软件和一些系统的缓冲区中记录下来的,缓冲区可以通过 logcat 命令来查看和使用.      一.使用logcat命令的目的: ...

  5. sql Server 发送邮件 错误类型及原因

    设置警报         在[常规项]中做以下设置 新建警报 设置警报名称 选择数据库 选择严重性     在[响应项]中可以做以下设置 选择要邮件通知的操作员 可以设置执行一个警报作业    在选项 ...

  6. Hibernate学习之对象持久化

    1.  对象持久化 对象的持久化就是把内存中对象形式的业务数据,转换成数据库中的关系数据形式的业务数据.广义理解,对象的持久化还包括内存与关系数据库之交换业务数据的各种操作. 2. 对象持久化模式 1 ...

  7. Isim你不得不知道的技巧(整理)

    来源:电子产品世界: 注:本文由本人多出整理所得,原文章图片不清晰,自己整理配图后重新发表 安装好ISE,系统已经自带了ISim仿真软件,相比于专业的仿真软件Modelsim,ISim是免费的,不用编 ...

  8. smarty、thinkphp中的html加载其他的html文件的方式

    1.smarty 在模板文件中,使用定界符 {include file="header.html"}  不可以省略.html 2.thinkphp的html文件中 <incl ...

  9. 【Linux常用工具】1.1 diff命令的三种格式

    diff是用来比较两个文本文件的差异的工具,它有三种格式,下面用实例介绍一下: 准备三个测试文件1.txt 2.txt 3.txt bixiaopeng@bixiaopengtekiMacBook-P ...

  10. struts 2学习笔记—初学struts 2

    首先我学习了struts 1.x与struts 2的区别: 1.struts 1.x的控制器类必须从Action类继承. 2.struts 2的控制器类可以是一个普通的类,也可以是ActionSupp ...