KMP算法细讲(豁然开朗)
一.KMP算法是如何针对传统算法修改的
用模式串P去匹配字符串S,在i=6,j=4时发生失配:
---------------------------------------------------------------------
i=6
S: a b a b c a d c a c b a b
P: a b c a c
j=4
---------------------------------------------------------------------
此时,按照传统算法,应当将P的第 1 个字符 a(j=0) 滑动到与S中第4个字符 b(i=3) 对齐再进行匹配:
---------------------------------------------------------------------
i=3
S: a b a b c a a d a c b a b
P: a b c a c
j=0
---------------------------------------------------------------------
这个过程中,对字符串S的访问发生了“回朔”(从 i=6 移回到 i=3)。
我们不希望发生这样的回朔,而是试图通过尽可能的“向右滑动”模式串P,让P中index为 j 的字符对齐到S中 i=5 的字符,然后试图匹配S中 i=6 的字符与P中index为 j+1 的字符。
在这个测试用例中,我们直接将P向右滑动3个字符,使S中 i=5 的字符与P中 j=0 的字符对齐,再匹配S中 i=6 的字符与P中 j=1 的字符。
---------------------------------------------------------------------
i=6
S: a b a b c a d c a c b a b
P: a b c a c
j=0
---------------------------------------------------------------------
二.求KMP算法中的next
举例说明:
按上述定义给出next数组的一个例子:
j 0 1 2 3 4 5 6 7
P a b a a b c a c
next[j] -1 0 0 1 1 2 0 1
查找对称串
申明一下:下面说的对称不是中心对称,而是中心字符块对称,比如不是abccba,而是abcabc这种对称。
详解:
将j导入next函数,即可求得, j=0时,next[]=-1; j=1时,k的取值为(,1)的开区间,所以整数k是不存在的,那就是第三种情况,next[]=; j=2时,k的取值为(,)的开区间,k从最大的开始取值,然后带入含p的式子中验证等式是否成立,不成立k取第二大的值。现在是k=,将k导入p的式子中得,p0=p1,即“a”=“b”,
显然不成立,舍去。k再取值就超出范围了,所以next[]不属于第二种情况,那就是第三种了,即next[]=; j=3时,k的取值为(,)的开区间,先取k=,将k导入p的式子中得,p0p1=p1p2,不成立。 再取k=,得p0=p2,成立。所以next[]=; j=4时,k的取值为(,)的开区间,先取k=,将k导入p的式子中得,p0p1p2=p1p2p3,不成立。 再取k=,得p0p1=p2p3,不成立。 再取k=,得p0=p3,成立。所以next[]=;
……
在已知next数组的前提下,字符串匹配的步骤如下:
i 和 j 分别表示在主串S和模式串P中当前正待比较的字符
在匹配过程中的每一次循环,若,i 和 j 分别增 1,
else,j 退回到 next[j]的位置,此时下一次循环是与
相比较。
void getnext(int *next, char *p)
{
int j = , k = -;
next[] = -;
while(j < lenp-1)//-1
{
if(k == - || p[j] == p[k])
{
j++;
k++;
next[j] = k;//当j==0时,已经求出了next[1]的值,所以j<lenp-1
}
else
k = next[k];
}
}
三.getNext函数的进一步优化
注意到,上面的getNext函数还存在可以优化的地方,比如:
i=3
S: a a a b a a a a b
P: a a a a b
j=3
此时,i=3、j=3时发生失配,next[3]=2,此时还需要进行 3 次比较:
i=3, j=2;
i=3, j=1;
i=3, j=0。
而实际上,因为i=3, j=3时就已经知道a!=b,而之后的三次依旧是拿 a 和 b 比较,因此这三次比较都是多余的。
此时应当直接将P向右滑动4个字符,进行 i=4, j=0的比较。
一般而言,在getNext函数中,next[i]=j,也就是说当p[i]与S中某个字符匹配失败的时候,用p[j]继续与S中的这个字符比较。
如果p[i]==p[j],那么这次比较是多余的(如同上面的例子),此时应该直接使next[i]=next[j]。
void getNextUpdate(const std::string& p, std::vector<int>& next)
{
next.resize(p.size());
next[] = -; int i = , j = -; while (i != p.size() - )
{
//这里注意,i==0的时候实际上求的是nextVector[1]的值,以此类推
if (j == - || p[i] == p[j])
{
++i;
++j;
//update
//next[i] = j;
//注意这里是++i和++j之后的p[i]、p[j]
next[i] = p[i] != p[j] ? j : next[j];
}
else
{
j = next[j];
}
}
}
假定p.size()为m,分析其时间复杂度的困惑在于,在while里面不是每次循环都执行 ++i 操作,所以整个while的执行次数不一定为m。
换个角度,注意到在每次循环中,无论 if 还是 else 都会修改 j 的值且每次循环仅对 j 进行一次修改,所以在整个while中 j 被修改的次数即为getNext函数的时间复杂度。
每次成功匹配时,++i; ++j; , 由于 ++i 最多执行 m-1 次,故++j也最多执行 m-1 次,即 j 最多增加m-1次;
对应的,只有在 j=next[j]; 处 j 的值一定会变小,由于 j 最多增加m-1次,故 j 最多减小m-1次。
综上所述,getNext函数的时间复杂度为O(m),
若带匹配串S的长度为n,则kmp函数的时间复杂度为O(m+n)。(有待验证)
四、kmp的应用优势
①快,O(m+n)的线性最坏时间复杂度;
②无需回朔访问待匹配字符串S,所以对处理从外设输入的庞大文件很有效,可以边读入边匹配。
大部分转自GoAgent
http://www.cnblogs.com/goagent/archive/2013/05/16/3068442.html
KMP算法细讲(豁然开朗)的更多相关文章
- KMP算法的理解
---恢复内容开始--- 在看数据结构的串的讲解的时候,讲到了KMP算法——一个经典的字符串匹配的算法,具体背景自行百度之,是一个很牛的图灵奖得主和他的学生提出的. 一开始看算法的时候很困惑,但是算法 ...
- 【讲●解】KMP算法
KMP算法 我们小组负责讲这个... 术语与规定 为了待会方便,所以不得不做一些看起来很拖沓的术语,但这些规定能让我们更好地理解\(KMP\)甚至\(AC\)自动机. 字符串匹配形式化定义如下: 假设 ...
- 讲不明白自杀系列:KMP算法
算法:KMP排序 算法分析 KMP算法是一种快速的模式匹配算法.KMP是三位大师:D.E.Knuth.J.H.Morris和V.R.Pratt同时发现的,所以取首字母组成KMP. 少部分图片来自孤~影 ...
- 详讲KMP算法
两个字符串: 模式串:ababcaba 文本串:ababcabcbababcabacaba KMP算法作用:快速在文本串中匹配到模式串 如果是穷举法的方式: 大家有发现,这样比效率很低的. 所以就需要 ...
- Python 细聊从暴力(BF)字符串匹配算法到 KMP 算法之间的精妙变化
1. 字符串匹配算法 所谓字符串匹配算法,简单地说就是在一个目标字符串中查找是否存在另一个模式字符串.如在字符串 "ABCDEFG" 中查找是否存在 "EF" ...
- KMP算法
KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...
- Java数据结构之字符串模式匹配算法---KMP算法
本文主要的思路都是参考http://kb.cnblogs.com/page/176818/ 如有冒犯请告知,多谢. 一.KMP算法 KMP算法可以在O(n+m)的时间数量级上完成串的模式匹配操作,其基 ...
- KMP 算法
KMP 是一个字符串匹配算法.之所以称之为KMP 是因为这个算法是由Knuth.Morris.Pratt三个提出来的. 这个算法能干什么呢 ? 我想到的有三个: 1. 告诉你一个串是否是另外一个串的子 ...
- (原创)详解KMP算法
KMP算法应该是每一本<数据结构>书都会讲的,算是知名度最高的算法之一了,但很可惜,我大二那年压根就没看懂过~~~ 之后也在很多地方也都经常看到讲解KMP算法的文章,看久了好像也知道是怎么 ...
随机推荐
- 如何手玩5h uoj215 果冻运输得到 AC
最近在大力练习提答颓提答,听说果冻运输很好玩就来试试. 然后玩的停不下来 QAQ ... 于是开一篇博客写一下每个点的解法.(一个个手玩出来的..) 首先我们每次都算什么下一步完后会发生什么在大脑中演 ...
- cookie跨站脚本漏洞解决方案
近日项目碰到一个跨脚本注入的问题: 这安全测评工具也是厉害了,直接将脚本注入到cookie里头,以前没有碰到这样的情况. 之前写过一篇文章过滤跨脚本注入的问题.<浅谈XSS攻击原理与解决方法&g ...
- git 查看、创建、切换、删除、重命名和推送分支
1.查看本地所有分支:前面有 “*” 的是当前所处的分支 $ git branch test-A * test-B 2.查看本地和远程服务器的所有分支: $ git branch -a test-A ...
- shell中的输入输出和编程中的变量(shell 03)
shell中的输入输出标准输入:键盘标准输出:终端显示器>> 追加是换行追加的echo -n 不尾随换行符 -e 启用解释反斜杠的转义功能 -E 禁用解释反斜杠的转义功能(默认) --he ...
- jenkins学习(1)
(1) 按照JAVA, 增加环境变量JAVA_HOME = C:\Program Files\Java\jdk1.8.0_31 增加环境变量CLASS_PATH = ...
- python学习(十一) 文件和流
11.1 打开文件 >>> f = open(r'c:\text\somefile.txt'), 第一个参数是文件名,必须有:第二个是模式:第三个参数是缓冲. 11.1.1 文件模 ...
- 进程之 Process join方法其他属性与进程Queue
Process join方法 以及其他属性 在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况 情况一:在主进程的任务与子进程的任务彼此独立的情 ...
- oracle创建表空间、创建用户
create user user_name identified by user_name create temporary tablespace user_name_temp tempfile '/ ...
- IDA Pro 权威指南学习笔记(五) - IDA 主要的数据显示窗口
在默认配置下,IDA(从 6.1 版开始)会在对新二进制文件的初始加载和分析阶段创建 7 个显示窗口 3 个立即可见的窗口分别为 IDA-View 窗口.函数窗口和消息输出窗口 可以通过 View - ...
- python arp欺骗
使用python构造一个arp欺骗脚本 import os import sys from scapy.all import * import optparse def main(): usage=& ...