KMP算法是由Knuth,Morris,Pratt共同提出的算法,专门用来解决模式串的匹配,无论目标序列和模式串是什么样子的,都可以在线性时间内完成,而且也不会发生退化,是一个非常优秀的算法,时间复杂度的上界是O(n+m)。

  那么我们现在就来研究一下KMP算法究竟是个什么东西,我这里就尽量说的简洁一点,注重应用,原理的话还是需要通过练习来巩固,或者通过本文的参考链接继续深入地看。本文的很多思路都来自于参考。

1.为什么普通的字符串匹配算法会那么慢?

  这个问题很好理解,暴力的字符串匹配算法,其实就是个组合的过程,一般来说有两个量,一个是i一个是j,i在目标串上移动,j在模式串上移动,如果target[i]==text[j],那么i++,j++,如果不等于,不好意思,请 i 回到开始匹配成功的下一个位置,j=0,重复匹配,可见这样的算法的复杂度是O(n*m),相当地慢,这种算法没什么技巧。

  当然了如果学过数据结构的人都知道,对于字符串匹配还有比暴力法好得多的一种算法,那就是散列法,散列法通过算字符串的散列值来匹配模式串,我们只用把模式串散列一次然后就可以把维持m长度的指针从目标串的0位置匹配到n-m位置就可以了,这个算法的复杂度是上界是O(n),看上去很不错,但是事实上如果模式串很大的话,我们要找到一个很好的散列函数(第一个保证散列值都是唯一的(不可能用链表法,因为可能散列值会很大,重复很多效率就下降了),散列值最好是能从上一个推出下一个的),而且要开辟一个相当大的空间来储存散列值。

  当然散列法也是一个很好的算法,用来算某些特定的问题会很快,但是有没有更好的呢?有,那就是KMP算法。

2.KMP算法的雏形

  KMP算法的根本目的就是想让i不后退(散列法也是这样想的),这就要求我们把匹配过的信息进行储存,KMP算法用到一个next数组,是整个算法的关键。

  讲next数组之前我们先来明白一下什么是模式串的前缀后缀最长公共长度,先来看一个表:

  

  从这个表我们可以很清楚看到,所谓的前缀和后缀其实就是在第i个位置从前往后数和从pos位置从后往前数的一样的子串的长度,但是找到这个关系可以用来干嘛呢?

  我们现在来看一下KMP算法的操作流程,再来看这个前缀和后缀与算法之前有什么关系:

  KMP算法:

  1.构建next数组

  2.模式串与目标串进行匹配,假设现在模式串和目标串已经匹配到j和i,那么

    如果target[i]==text[j],则i++,j++(这个和暴力算法一致)

    如果target[i]!=text[j],则使j通过next数组跳转到前一个j(假设是k位置,的相当于是使j匹配从pos向前移动pos-j个位置,再判断target[i]是否等于text[k],

    否则继续执行k=next[k],直到target[i]==text[k]。如果无法找到合适的元素使target[i]==text[k],则另k=0,且i++;

  可以看到next数组是用来解决暴力解法的当匹配失败时就要j=0的缺点,可以让j跳转到某个合适的位置,继续匹配

  当然了这个位置也不是随便乱选的,而这个位置就是刚好是当前元素前面元素的前缀后缀最长公共长度的后一个位置,当然了我们要要从当前位置跳转,我们关注的是当前位置前面的元素的情况,所以我们把上面那个表所有值往前移动一个单位,然后把0位置设置成-1就得到了next表了。

3.KMP算法的关于模式串移动的原理

  大家看到这里可能会很疑惑,究竟为什么跳转到前面元素的前缀和后缀最长公共的后一个位置前面一个位置就是可以的呢?这个的确不太好说明,我们只从例子上说明问题,我们先来举一个例子:

  

  从面例子中,其实移动到前注意和后缀最长公共位置是合理的,因为我们前后缀是相等的,我们移动后可以保证当前元素前面的元素都是匹配的,比如图中这个例子,移动以后AC还是和前面配过的元素一样,最后完成匹配,最后当然了,如果移动后还失配,还是需要相同的移动方法。直到移动到模式串的最前端为止(移动到最前端说明已经找不到任何匹配的元素了,相当于重新匹配模式串了)。

4.KMP算法的Next数组的代码构建

  那么说了,我们最终的目的还是要想办法构建next数组,那么究竟怎么构建呢?

  这里我们用到了递推法,通过前面的元素的结果来地推到当前的结果,其实这个递推的过程和KMP匹配的过程差不多

1.初始化Next[0]=-1,k=-1,i=0;

2.如果text[i]==text[k]或者k==-1,则i++,k++,Next[i]=k;

 如果text[i]!=text[k],则使k通过next数组跳转到前一个k,,再判断text[i]是否等于text[k],

 否则继续执行k=next[k],直到k==-1。如果无法找到合适的元素使text[i]==text[k]

  代码很好写,如下:

 typedef int Position;

 void Get_Next(const string &str_text, int *const _next)
{
Position i = , k = -;
int slen = str_text.length();
_next[] = -; while (i < slen)
{
if (k == - || str_text[i] == str_text[k])
{
k++;
i++;
_next[i] = k;
16      
}
else k = _next[k];
}
}

  思想和匹配的时候的是一样的,也是通过递推来得到最后前缀后缀的最长公共元素的长度,从而确定k究竟要跳到什么位置。

  当然了,这里还可以优化一下,我们知道我们在KMP算法的时候如果pattern[i]和text[j]不相等的时候我们还是要往前跳转的,那么为什么我们不在next数组里面就完成这件事情呢?

 typedef int Position;

 void Get_Next(const string &str_text, int *const _next)
{
Position i = , k = -;
int slen = str_text.length();
_next[] = -; while (i < slen)
{
if (k == - || str_text[i] == str_text[k])
{
k++;
i++;
_next[i] = str_text[i] != str_text[k] ? k : _next[k];
//当i和k相等,直接引用next[k],这样就可以避免重复配对,直接跳转到这个位上不重复的前缀上
}
else k = _next[k];
}
}

  这样,我们就完成了优化,当KMP进行跳转的时候,我们将会用最小的步数跳到合适的位置。

5.KMP算法的模板

  最后,我们根据之前KMP算法的原理,把KMP算法写出

 typedef int Position;

 bool KmpSearch(const string &,const  string &,int *const);
void Get_Next(const string &,int *const); bool KmpSearch(const string &pattern, const string &str_text, int *const _next)
{
Position i = , j = ;
int plen = pattern.length(), slen = str_text.length();
Get_Next(str_text, _next); while (i < plen &&j < slen)
{
if (j == - || pattern[i] == str_text[j])
{
//j==-1表示已经回溯到目标串的前部,最前部都不符合当前字符,i要前进一个单位
i++;
j++;
}
else j = _next[j];//直接跳转到最小的前缀去
}
return j == slen;
} void Get_Next(string str_text, int *const _next)
{
Position i = , k = -;
int slen = str_text.length();
_next[] = -; while (i < slen)
{
if (k == - || str_text[i] == str_text[k])
{
k++;
i++;
_next[i] = str_text[i] != str_text[k] ? k : _next[k];
//当i和k相等,直接引用next[k],这样就可以避免重复配对,直接跳转到这个位上不重复的前缀上
}
else k = _next[k];
}
}

  最后关于KMP算法的时间复杂度的分析,我不作严格的证明,网上的很多文章也没有给出很严格的证明(计算机科学和数学密不可分,真正严格的证明应该是可以用数学符号来表示的,可是目前为止我看到的任何一篇文章都没有给出一个很严谨的证明),在《Fast Pattern Matching In Strings》——By Knuth-Morris-Pratt(事实上这篇论文是三位大神联合署名的,KMP算法的名字就是这三位大神名字首字母连起来)这篇论文有写,但是为了文章简洁一点还是不纠结了)。

  我们来个形式上的推导:因为对于模式串的i,最多移动的位置是n,而对于j,最坏的情况是到最后一个元素才不匹配,j跳到字串的最开始,重新匹配,但是i又不向后移动,所以时间复杂度的上界是O(m+n)。

6.总结

  KMP算法是一个优化非常强的算法,事实上KMP的Next跳转表是一个离散数学的重要工具:DFA(有限状态自动机,名字很高大上,其实就是一个跳转记录的东西)的一个简化版,事实上KMP是巧妙地根据字符串的前缀和后缀的联系来实现的,为字符串处理开辟了一个崭新的道路

  参考:http://blog.csdn.net/joylnwang/article/details/6778316

     http://blog.csdn.net/heiyeshuwu/article/details/42883293

     http://blog.csdn.net/v_july_v/article/details/7041827

  KMP算法广泛运用于ACM中,当然了工程中也可以运用,是很实用的算法,但是很有趣的是,除了比较的量很大的情况下,KMP的效率和C的内置函数strstr()的效率其实是差不多的,而strstr是直接拿两个字符串来比较的,算法复杂度是O(n*m),可能是一般比较的字串的长度都很小的缘故。

  另外单模匹配算法除了KMP还有BM和Sunday算法,理论上后面两个算法的比KMP更优,不过其实应该也是快不了多少。

KMP单模快速字符串匹配算法的更多相关文章

  1. BM算法和Sunday快速字符串匹配算法

    BM算法研究了很久了,说实话BM算法的资料还是比较少的,之前找了个资料看了,还是觉得有点生涩难懂,找了篇更好的和算法更好的,总算是把BM算法搞懂了. 1977年,Robert S.Boyer和J St ...

  2. 【原创】通俗易懂的讲解KMP算法(字符串匹配算法)及代码实现

    一.本文简介 本文的目的是简单明了的讲解KMP算法的思想及实现过程. 网上的文章的确有些杂乱,有的过浅,有的太深,希望本文对初学者是非常友好的. 其实KMP算法有一些改良版,这些是在理解KMP核心思想 ...

  3. 字符串匹配算法之————KMP算法

    上一篇中讲到暴力法字符串匹配算法,但是暴力法明显存在这样一个问题:一次只移动一个字符.但实际上,针对不同的匹配情况,每次移动的间隔可以更大,没有必要每次只是移动一位: 关于KMP算法的描述,推荐一篇博 ...

  4. Boyer-Moore 字符串匹配算法

    字符串匹配问题的形式定义: 文本(Text)是一个长度为 n 的数组 T[1..n]: 模式(Pattern)是一个长度为 m 且 m≤n 的数组 P[1..m]: T 和 P 中的元素都属于有限的字 ...

  5. 4种字符串匹配算法:KMP(下)

    回顾:4种字符串匹配算法:BS朴素 Rabin-karp(上) 4种字符串匹配算法:有限自动机(中) 1.图解 KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R ...

  6. 字符串匹配算法——BF、KMP、Sunday

    一:Brute force 从源串的第一个字符开始扫描,逐一与模式串的对应字符进行匹配,若该组字符匹配,则检测下一组字符,如遇失配,则退回到源串的第二个字符,重复上述步骤,直到整个模式串在源串中找到匹 ...

  7. Python 细聊从暴力(BF)字符串匹配算法到 KMP 算法之间的精妙变化

    1. 字符串匹配算法 所谓字符串匹配算法,简单地说就是在一个目标字符串中查找是否存在另一个模式字符串.如在字符串 "ABCDEFG" 中查找是否存在 "EF" ...

  8. 字符串匹配算法 - KMP

    前几日在微博上看到一则微博是说面试的时候让面试者写一个很简单的字符串匹配都写不出来,于是我就自己去试了一把.结果写出来的是一个最简单粗暴的算法.这里重新学习了一下几个经典的字符串匹配算法,写篇文章以巩 ...

  9. 字符串匹配算法——KMP算法学习

    KMP算法是用来解决字符串的匹配问题的,即在字符串S中寻找字符串P.形式定义:假设存在长度为n的字符数组S[0...n-1],长度为m的字符数组P[0...m-1],是否存在i,使得SiSi+1... ...

随机推荐

  1. protobuf

    1.下载地址:https://code.google.com/p/protobuf/downloads/list 安装 ./configure && make && m ...

  2. 网站SEO优化之添加Sitemap文件。

    Sitemap.xml 故名思意就是站点地图文件,可以指引Google spider 收录相应网页.正确地使用Google Sitemap,可以确保让Google spider 不遗漏网站内的任何页面 ...

  3. boss设计参考的脑图

  4. svn更改默认服务启动目录

    配置文件位于 /etc/sysconfig/svnserve 修改为自己的目录

  5. Java 7 Concurrency Cookbook 翻译 第一章 线程管理之五

    九.使用线程本地变量 一个并发程序的最关键特征就是共享数据.这个特性在那些继承了 Thread 类或者 实现了 Runnable 接口的对象上显得更加重要. 如果你创建一个实现了 Runnable 接 ...

  6. Camel——涨知识了,骆驼命名法

    骆驼式命名法(Camel-Case)又称驼峰命名法,是电脑程式编写时的一套命名规则(惯例).正如它的名称CamelCase所表示的那样,是指混合使用大小写字母来构成变量和函数的名字.程序员们为了自己的 ...

  7. BZOJ2555——SubString

    0.题目很短,就不概括了 给你一个字符串init,要求你支持两个操作 (1):在当前字符串的后面插入一个字符串 (2):询问字符串s在当前字符串中出现了几次?(作为连续子串) 你必须在线支持这些操作. ...

  8. 开源多线程性能测试工具-sysbench

    导读 sysbench是一款开源的多线程性能测试工具,可以执行CPU/内存/线程/IO/数据库等方面的性能测试.数据库目前支持MySQL/Oracle/PostgreSQL.本文主要演示Mysql测试 ...

  9. Unity手游之路<十二>手游资源热更新策略探讨

    http://blog.csdn.net/janeky/article/details/17666409 上一次我们学习了如何将资源进行打包.这次就可以用上场了,我们来探讨一下手游资源的增量更新策略. ...

  10. vs2012+qt5.2.0环境搭建

    1.安装vs2012: 2.下载Qt 5.2.0 for Windows 32-bit(VS 2012, 579 MB) 和 Visual Studio Add-in 1.2.2for Qt5 注意: ...