真正理解KMP算法
作者:jostree 转载请注明出处 http://www.cnblogs.com/jostree/p/4403560.html
所谓KMP算法,就是判断一个模式串是否是一个字符串的子串,通常的算法当模式串失配后需要回溯原串和模式串,原串从上次开始匹配的下一个字母开始来匹配模式串的第一个字母。举一个例子,原串为ABABABCD,模式串为ABABCD,如图1一直从头开始匹配,当匹配到第5个红色字母时,发现A和C失配,通常的算法需要回溯原串从第二个字符开始,模式串从第一个字符开始重新匹配,如图2所示:

图1 字符串失配

图2 传统方法回溯
我们发现,回溯原串是不必要的,因为我们可以发现,回溯到图二的情况时一定会失配,原因是在图1中模式串的前两个字母已经和原串的前两个字母匹配,并且模式串的前两个字母不同,所以当模式串第一个字母和原串的字母一定失配,那么我们就没有必要去回溯原串,只需把模式串向右移动就行了。那么向右移动到什么位置呢?如图3所示,我们发现模式串的前缀深红色的AB与模式串失配的第5个字符C之前的绿色后缀AB完全相同,有因为绿色的模式串后缀AB与原串匹配,因此我们可以断定模式串的前缀AB一定可以和原串绿色的AB相匹配,从而直接可以把模式串向后移动两位,得到图4的样子,继续进行匹配。

图3 模式串的前后缀相同

图4 KMP算法失配后向右移动两位
那么我们如何计算模式串每次移动的位置呢,对于模式串的每一位我们都要预处理出一个其失配后需要移动到的位置,即next数组,其中next数组第i位的含义为:模式串开始到第i位之前的字符串的前缀字符串与后缀字符串相同的最大长度,并另next[0]=-1。从而我们可以口算出ABABCD的next数组:
表1 口算next数组
| 模式串 | A | B | A | B | C | D |
| 前缀后缀匹配 | 无 | 无 | 无 | 前缀A=后缀A | 前缀AB=后缀AB | 无 |
| next | -1 | 0 | 0 | 1 | 2 | 0 |
多算几个模式串的next数组我们就会发现,我们可以利用前面的字母的next数组的值来计算当前字母的next数组,例如对与ABABCD中的第5个字母C,因为他的前一个字母B的next数组的值为1也就是说第1个前缀字母A和B的后缀字母A相同,我们并不需要再次进行比较,而是直接比较p[4]与p[next[4]]是否相同我们发现都是B,从而第5个字符C的next数组的值就等于其前一个字母B的next数组值+1。如果不匹配怎么办?我们只需再次向前比较p[4]与p[next[next[4]]即可,一直到相等或者next数组的值为-1。这样我们就可以很轻松的计算next数组了。
其计算next数组和KMP匹配的代码如下,其中KMP函数返回模式串与原串匹配的次数:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <limits.h>
#include <iostream> using namespace std;
vector<int> GetNext(string p)
{
vector<int> next;
next.push_back(-);
int k = -;
int j = ;
while(j < p.size())
{
if( k == - || p[j] == p[k] )
{
++k;
++j;
next.push_back(k);
}
else
{
k = next[k];
}
}
return next;
} int KMP(string p, string s, vector<int> next)
{
int i = ;
int j = ;
int res = ;
while( i < s.size() )
{
if( j == p.size() )
{
res++;
j = next[j];
}
else if( j == - || s[i] == p[j] )
{
i++;
j++;
}
else
{
j = next[j];
}
}
if( s[i] == p[j] )
{
res++;
}
return res;
} int main(int argc, char *argv[])
{
int n;
cin>>n;
while( n-- )
{
string p;
string o;
cin>>p>>o;
vector<int> next = GetNext(p);
cout<<KMP(p, o, next)<<endl;
}
}
next数组的优化:使用这种方法计算next数组时我们会发现一个问题,例如对于模式串为ABAB,原串为ABACABAB的字符串。我们可以得到模式串的next数组为-1,0,0,1。从而当其进行匹配第一次失配时如图5所示,根据失配的第4个B的next值为1,从而模式串下标为1的字母B与原串再次匹配,如图6所示:

图5 失配

图6 模式串下标为1的字母B与其进行匹配
我们可以发现,这次的匹配一定使失败的,因为在我们的模式串的下标为3的字母B,其p[3]=p[next[3]]='B',有因为p[3]与原串失配,所以p[next[3]]也一定与原串失配,因此我们在构造模式串时,需要判断p[i]是否与p[next[i]]相等,如果不想等,赋值next即可,如果相等,则需要把对next[next[i]的值赋给next[i],对于这个例子,因为p[3]=p[next[3]]='B',所以另next[3]=next[next[3]] = -1。即可得到优化后的next数组。为-1,0,0,-1。
代码如下:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <limits.h>
#include <iostream> using namespace std;
vector<int> GetNext(string p)
{
vector<int> next;
next.push_back(-);
int k = -;
int j = ;
while(j < p.size())
{
if( k == - || p[j] == p[k] )
{
++k;
++j;
if( p[j] != p[k] )
{
next.push_back(k);
}
else
{
next.push_back(next[k]);
}
}
else
{
k = next[k];
}
}
return next;
} int KMP(string p, string s, vector<int> next)
{
int i = ;
int j = ;
int res = ;
while( i < s.size() )
{
if( j == p.size() )
{
res++;
j = next[j];
}
else if( j == - || s[i] == p[j] )
{
i++;
j++;
}
else
{
j = next[j];
}
}
if( s[i] == p[j] )
{
res++;
}
return res;
} int main(int argc, char *argv[])
{
int n;
cin>>n;
while( n-- )
{
string p;
string o;
cin>>p>>o;
vector<int> next = GetNext(p);
cout<<KMP(p, o, next)<<endl;
}
}
真正理解KMP算法的更多相关文章
- 理解 KMP 算法
KMP(The Knuth-Morris-Pratt Algorithm)算法用于字符串匹配,从字符串中找出给定的子字符串.但它并不是很好理解和掌握.而理解它概念中的部分匹配表,是理解 KMP 算法的 ...
- 深入理解KMP算法
前言:本人最近在看<大话数据结构>字符串模式匹配算法的内容,但是看得很迷糊,这本书中这块的内容感觉基本是严蔚敏<数据结构>的一个翻版,此书中给出的代码实现确实非常精炼,但是个人 ...
- KMP算法详解 --- 彻头彻尾理解KMP算法
前言 之前对kmp算法虽然了解它的原理,即求出P0···Pi的最大相同前后缀长度k. 但是问题在于如何求出这个最大前后缀长度呢? 我觉得网上很多帖子都说的不是很清楚,总感觉没有把那层纸戳破, 后来翻看 ...
- 从头到尾测地理解KMP算法【转】
本文转载自:http://blog.csdn.net/v_july_v/article/details/7041827 1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP ...
- 深入理解KMP算法之续篇
前言: 纠结于KMP已经两天了,相较于本人之前博客中提到的几篇博文,本人感觉这篇文章更清楚地说明了KMP算法的来龙去脉. http://www.cnblogs.com/goagent/archive/ ...
- 理解KMP算法
母串:S[i] 模式串:T[i] 标记数组:Next[i](Next[i]表示T[0~i]最长前缀/后缀数) 先来讲一下最长前缀/后缀的概念 例如有字符串T[6]=abcabd接下来讨论的全部是真前缀 ...
- KMP算法 --- 深入理解next数组
在KMP算法中有个数组,叫做前缀数组,也有的叫next数组. 每一个子串有一个固定的next数组,它记录着字符串匹配过程中失配情况下可以向前多跳几个字符. 当然它描述的也是子串的对称程度,程度越高,值 ...
- 从有限状态机的角度去理解Knuth-Morris-Pratt Algorithm(又叫KMP算法)
转载请加上:http://www.cnblogs.com/courtier/p/4273193.html 在开始讲这个文章前的唠叨话: 1:首先,在阅读此篇文章之前,你至少要了解过,什么是有限状态机, ...
- KMP算法的一次理解
1. 引言 在一个大的字符串中对一个小的子串进行定位称为字符串的模式匹配,这应该算是字符串中最重要的一个操作之一了.KMP本身不复杂,但网上绝大部分的文章把它讲混乱了.下面,咱们从暴力匹配算法讲起,随 ...
随机推荐
- 基于css3的文字3D翻转特效
一款基于css3的文字3D翻转特效.这款特效当鼠标经过文字的时候3D翻转显示阴影.效果图如下: 在线预览 源码下载 实现的代码. html代码: <div class="compo ...
- android图片特效处理之模糊效果
这篇将讲到图片特效处理的模糊效果.跟前面一样是对像素点进行处理,算法是通用的,但耗时会更长,至于为什么,看了下面的代码你就会明白. 算法: 一.简单算法:将像素点周围八个点包括自身一共九个点的RGB值 ...
- 减肥App计划
写在前面 最近公司需求不多,正好研究一下 App 瘦身的办法,写了点小总结. 如果你不知道下面几个问题,不妨可以看看文章. 使用 .xcassets 有什么好处? @1x .@2x 和 @3x 会 ...
- 跨平台轻量级redis、ssdb代理服务器(C++ 11编写)
dbproxy 是我业余采用C++11编写的跨平台代理服务器(并使用lua和自己的网络库),以扩展系统负载,同时使用多个后端数据库,后端数据库支持redis和ssdb. 需要由用户自己编写lua脚本控 ...
- Excel两行交换及两列交换,快速互换相邻表格数据的方法
经常使用办公软件的人可能有遇到过需要将Excel相邻两行数据相互交换的情况,需要怎么弄才最方便呢?您还是像大家通常所做的那样先在Excel文件相应位置插入一个新的空白行然后在复制粘贴数据然后删除原来那 ...
- VMware系统运维(十三)部署虚拟化桌面 Horizon View Agent 5.2安装
1.先打开安装程序如下所示 2.点击"下一步" 3.不接受,你能装吗?点击"下一步" 4.选择安装位置,点击"下一步" 5.开启3389和3 ...
- Linux下MySQL主从同步配置
Centos6.5 MySQL主从同步 MySQL版本5.6.25 主服务器:centos6.5 IP:192.168.1.101 从服务器:centos6.5 IP:192.168.1.102 一. ...
- 使用aop记录数据库操作的执行时间
在项目中,我们往往需要记录数据库操作的时间,根据操作时间的不同,分别记录不同等级的日志. 首先我们可以写一个类实现MethodInterceptor接口: import org.aopalliance ...
- [__NSCFNumber length]: unrecognized selector sent to instance 0x8b3c310
出现这种问题一般是你把int类型的数值赋给了NSString. 比如: 你定义了一个NSString类型的属性sex,但是服务端返回的sex字段实际上是NSNumber类型, 你直接把NSNumber ...
- 百度编辑器UEditor,地址栏传值长度有限-在webConfig配置
<system.web> <httpRuntime requestValidationMode=" ></httpRuntime> <compila ...