一、本文简介

本文的目的是简单明了的讲解KMP算法的思想及实现过程。

网上的文章的确有些杂乱,有的过浅,有的太深,希望本文对初学者是非常友好的。

其实KMP算法有一些改良版,这些是在理解KMP核心思想后的优化。

所以本文重点是讲解KMP算法的核心,文章最后会有涉及一些改良过程。

二、KMP算法简介

KMP算法是字符串匹配算法的一种。它以三个发明者命名,Knuth-Morris-Pratt,起头的那个K就是著名科学家Donald Knuth。

三、KMP算法行走过程

首先我们先定义两个字符串作为示例,被匹配串 S = "abcabdabcabeab",匹配串 T = "abcabeab"。

我们的目标就是确定S中是否包含T。

KMP算法的核心是分析匹配串 T 的特征,看看匹配串 T 能告诉我们什么信息。我把 T 着色,如下

T = "abcabeab"

现在看起来似乎比较明显了,三个着色点都是重复的 "ab",似乎这个 T 能告诉我们它有重复的子串"ab"可以利用。那么它们到底怎么用?先不讲具体怎么用,先走一遍KMP这个过程,但是大家需要留意这个"ab"。

1.  

我们发现T在匹配成功 "abcab" 后到"e"时和S子串的"d"匹配不成功了。

这个时候我们可以得到的先验就是目前T匹配过的S子串,就是"abcab"。而T本身就是"ab"重复的。所以"abcab"可以直接跳到第二个重复"ab"的位置,因为"abcab"中其它字符串开头不可能产生和T对应的匹配,这是很直观的。

因此T应该直接后移三个位置,并且用第三位的"c"和S刚才不匹配的"d"进行比较。

可以发现,S匹配的过程是不会回退的。因此匹配过程是S从头到尾的一遍扫描(中间可能因为匹配成功退出),所以这个查找过程是O(N)的复杂度。

2.

此时的匹配串是 "ab", 它告诉我们目前匹配的是"ab"。这个不像"abcab"出现了重复"ab",所以我们知道包括S[5]的"d"之前的子串是垃圾串。因此跳过S[5]重新开始匹配T。

最后完成匹配,可见,这样一次对S的扫描完成了对T的查找。

那么对于机器如何实现?第四节会分析。

四、KMP算法核心解析

对于上面的过程,我们抽离出来的话,问题的根本就是对T串重复情况的一个判定。不管S是什么串,只要对T的构造模式分析清楚就可以完成上述跳转过程。

因此需要一个数组记录这个T的 模式函数。

这里先给出这个T的模式函数

1.每个字母对应模式函数的值就是匹配到当前位置 i 后,下一次T开始进行比较的下标。

2.而S的移动长度为 i - F(i-1)。

对应上面两个问题

1.比如上面的匹配到的 "abcab" ,是T匹配到 T[5] 即"e"的时候出错的。那么我们需要查看上一个字符的模式函数值,因为上一个函数值才代表了已经匹配的串。

发现F(4)=2,说明下次比较从T[2]即"c"开始。因为"abcab"有重复"ab",第一个"ab"不需要比较。

2.而S下标移动多少呢?S下标的移动即找到T的初试位置对应的S的下标。对应上面第二张图,S[3]和T的移动对应起来了。3的获得就是通过上面公式 5-F(4)得到的。其实这个结果和1得到匹配位置的思路是一样的,不过又向前移动对齐了开头。

为什么如此构造这个模式函数?

就是因为F值表示了对于位置 i, T[i]有无重复,并且重复下标的位置在哪(F[i])。既然我们获取了重复下标的位置,那么其它的相关值可以推出来了。

基于这个思路,再给出另外两个T串的模式函数值,帮助大家思考。

1.

2.

如何快速构造这个模式函数?

这个留给大家思考一下了,应该也比较直接了,注意是查找重复位置。如果不大明白,可以参考下面的代码。

五、KMP算法实现

再添加个例子帮助大家思考:

S1 = "aaaaaaaaaaaaaaaab"  S2 = "aaaaafaaaaaaaaab"   T = "aaaab"

对于S1和S2两种情况,应如何匹配。

代码如下:

 1 /*
2 return val means the begin pos of haystack
3 -1 means no matching substring
4 */
5 int KMP(char *haystack, char *needle) {
6 // pre-process
7 if(haystack[0] == 0 && needle[0] == 0)
8 return 0;
9
10 int i, j, k, min, cur;
11
12 //construct F(t) in vector len
13 vector<int > len;
14 len.push_back(0);
15 for(i=1; needle[i] != 0; i++){
16 if(len[i-1] == 0){
17 if(needle[i] == needle[0])
18 len.push_back(1);
19 else
20 len.push_back(0);
21 }else{
22 if(needle[i] == needle[len[i-1]])
23 len.push_back(len[i-1]+1);
24 else
25 len.push_back(0);
26 }
27 }
28 // KMP finder
29 j = 0;
30 for(i=0; haystack[i] != 0; ) {
31 // matching
32 for(; needle[j] != 0; j++) {
33 if(haystack[i+j] != needle[j])
34 break;
35 }
36 //finded
37 if(needle[j] == 0)
38 return i;
39 else{ // jump
40 if(j){
41 cur = j - len[j-1];
42 i += cur;
43 j = len[j-1];
44 }else{
45 j = 0;
46 i++;
47 }
48 }
49 }
50 //match failed
51 return -1;
52 }

六、KMP算法改进

KMP算法有一些改进版本加速查找,一般可以通过S串中的一些信息加速匹配过程。

比如若  S = "aaaaaaaafaaaaaaaaaaaab", T = "aaaaaaab"。

在查找过程中,S中间的 "f" 起到了阻挡作用。但是由于我们只是考虑T的先验信息,遇到"f" 不匹配会导致T每次后移一步进行新的匹配,直到T的开头碰到了"f"。

但是如果我们加入"f"这个原串S的信息,由于"f" != "b" && "f" != "a" && i-1 = F(i-1) ,所以直接跳到"f"后进行新的匹配会更快速的查找。

但是这些改进都是基于KMP基础算法之上的,因此把握核心要点不仅省时省力,更能有效扩展。

七、参考

[1] 《字符串匹配的KMP算法》 http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

[2] 算法导论

转载请注明出处~   http://www.cnblogs.com/xiaoboCSer/p/4237941.html

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

【原创】通俗易懂的讲解KMP算法(字符串匹配算法)及代码实现的更多相关文章

  1. KMP Algorithm 字符串匹配算法KMP小结

    这篇小结主要是参考这篇帖子从头到尾彻底理解KMP,不得不佩服原作者,写的真是太详尽了,让博主产生了一种读学术论文的错觉.后来发现原作者是写书的,不由得更加敬佩了.博主不才,尝试着简化一些原帖子的内容, ...

  2. (原创)详解KMP算法

    KMP算法应该是每一本<数据结构>书都会讲的,算是知名度最高的算法之一了,但很可惜,我大二那年压根就没看懂过~~~ 之后也在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么 ...

  3. 数据结构20:KMP算法(快速模式匹配算法)详解

    通过上一节的介绍,学习了串的普通模式匹配算法,大体思路是:模式串从主串的第一个字符开始匹配,每匹配失败,主串中记录匹配进度的指针 i 都要进行 i-j+1 的回退操作(这个过程称为“指针回溯”),同时 ...

  4. KMP算法字符串查找子串

    题目: 经典的KMP算法 分析: 和KMP算法对应的是BF算法,其中BF算法时间复杂度,最坏情况下可以达到O(n*m),而KMP算法的时间复杂度是O(n + m),所以,KMP算法效率高很多. 但是K ...

  5. KMP 算法 & 字符串查找算法

    KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...

  6. 编程算法 - 字符串的排列 代码(C)

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u012515223/article/details/35593485 字符串的排列 代码(C) 本文 ...

  7. Java KMP算法代码

    1. KMP 算法(字符串匹配算法)较 BF(朴素的字符串匹配)算法有哪些改进 1) 在主串和子串匹配的过程中,主串不再回退,只改变子串的比较位置. 2) 为子串生成对应的next数组,每次匹配失败, ...

  8. 字符串KMP——用途广泛的字符串匹配算法 + 扩展KMP——特殊定义的字符串匹配

    引 入 引入 引入 " SY 和 WYX 在看毛片.(几 毛 钱买到的动作 片,毛 片) WYX 突然想回味一个片段,但是只记得台词里面有一句挺长的 " ∗ ∗ ∗ ∗ **** ...

  9. 基于KMP算法的字符串模式匹配问题

    基于KMP算法的字符匹配问题 反正整个清明都在纠结这玩意...差点我以为下个清明要给自己过了. 至于大体的理解,我就不再多说了(还要画图多麻烦鸭),我参考了以下两个博客,写的真的不错,我放了超链接,点 ...

随机推荐

  1. Poj OpenJudge 1068 Parencodings

    1.Link: http://poj.org/problem?id=1068 http://bailian.openjudge.cn/practice/1068 2.Content: Parencod ...

  2. JBoss部署项目log4j配置会造成死锁问题,浏览器访问一直pending状态

    今天将项目部署到JBoss服务器上,部署成功后,浏览器访问页面一直在等待响应. 查了很长时间,最后在服务器上通过jstack pid命令查看Java堆栈信息,发现了有两个线程死锁. 看到造成死锁的原因 ...

  3. 解决FPDF报错:FPDF error: Not a JPEG file / FPDF error: Not a PNG file

    最近有个项目需要用到FPDF,但是输出的时候报错: FPDF error: Not a JPEG file: http://***/data/attachment/forum/201603/19/10 ...

  4. php session学习笔记(实例代码)

    http  无状态协议 一个服务器向客户端发送消息的时候有三条信息 一是状态二是头信息三是内容 会话控制 让一个用户访问每个页面,服务器都知道是哪个用户访问 cookie cookie是通过头信息发送 ...

  5. ISO-9126 软件质量模型

    摘要 在软件开发过程中,软件的质量是一个重要的因素,而软件体系结构在整个过程中显得尤为重要.软件的质量需求是在开发初期的非功能性需求,对软件的体系结构影响很大.但是并不意味着一味的追求质量,必须在效率 ...

  6. Beaglebone Back学习四(GPIO实验)

    GPIO Beaglebone Back开发板引出了92个引脚,其中只有65个GPIO口可通过配置使用,由于引脚具有“复用”的特性,大约每个引脚有8种工作模式(Beagle System Refere ...

  7. android include中的控件调用

    项目中经常会有一些布局是重用的,但是如何来更好的利用这些布局中的控件 转: http://zhidao.baidu.com/link?url=GU93U8Wu31dfp7mKEx52hMJkxjFLC ...

  8. WPF常用数据绑定控件集合

    1.怎么用ListView控件把XML中的数据在界面上显示出来? <?xml version="1.0" encoding="utf-8" ?> & ...

  9. iOS sqlite数据库实现(转)

    转载自:http://www.cnblogs.com/macroxu-1982/archive/2012/10/01/2709960.html 1 实现过程添加libsqlite3组件 选择项目后,在 ...

  10. 【android-cocos2d-X iconv.h】在android下使用iconv

    (1) 下载文件 首先下载iconv文件  下载地址:http://download.csdn.net/detail/dingkun520wy/6703113 把解压后的iconv文件夹放到cocos ...