基于KMP算法的字符匹配问题

反正整个清明都在纠结这玩意...差点我以为下个清明要给自己过了。

至于大体的理解,我就不再多说了(还要画图多麻烦鸭),我参考了以下两个博客,写的真的不错,我放了超链接,点击就可以传送过去了。

(原创)详解KMP算法(点击跳转):图画的很棒,很好理解,一步步带你深入

KMP算法最浅显理解——一看就明白(点击跳转):对主要的疑问有很细致地回答

需要注意的是,两篇博客都是以字符数组下标为0处开始存储

我对next数组不是很理解,说是next[j]表示的是j下一个指向的模式串的位置,但还是很抽象。经过一番思索,我对其有了个人理解

next数组的存在是KMP算法的核心,它的意义,就是如上所说,模式串中j下一个指向的地方,但这样说不够确切,应该是这样:

当主串s与模式串t分别在s[i],t[j]处,他们不相同的时候,保持s[i]的位置不变,将t[j]移动到t[next[j]]处

至于next数组应该怎么得来,只要明白了上面所说的,next数组的作用后,得到next数组的方式就显而易见了

遍历模式串t,得到每个位置t[j]的next[j]的值,这很像我们做策划时候的紧急预案,“如果我比较的时候在这个位置不同了,那我应该把j指向哪呢?”

本着这种思想,我们就可以得到next数组,而这个过程实际上是模式串t自我比较的过程,很多博客中都有,不再赘述。

而整体的逻辑就很明朗了——先做好预案才能放心地工作,对吧——先得到next数组,再把模式串和主串进行比较

由于KMP基于BF暴力算法,所以建议先打一边BF算法,再在其基础上改成KMP,而且需要修改的地方不是很多,可以加深理解。

K的值

实际上,模式串的每个位置t[j]都会自己对应的k值,正式我们所求的next[j],比如k1=next[1],k2=next[2].......

不过有些人对k的值不理解:什么叫“相同的最大前缀和最大后缀长”?

其实我们可以用更通俗的方法理解前缀后缀:(以ABCABB为例)

  1. 把模式串的最后一个字符遮住,剩下的串是不是能分成很多子串?值得注意的是,这里的字串要包括未遮住部分的第一个字符,如遮住最后一个B,剩下的是ABCAB,从左到右看依次是A,AB,ABC,ABCA,ABCAB,他们就是后前缀
  2. 把模式串的第一个字符遮住,剩下的串是不是能分成很多子串?同样,这里的字串要包括未遮住部分的第一个字符,如遮住第一个A,剩下的是BCABB,从左到右看依次是B,BC,BCA,BCAB,BCABB,他们就是后缀

当我们比较到ABCABB的最后一个B的时候,前面已经比较过的,和主串相同的部分自然是ABCAB。对于这部分来说前缀里的AB(ABC的AB)和后缀里的AB(CAB的AB)是相同的,并且也是最长的相同串。他们的长度2就是最后一个B的K值:如果比较到B不相等的时候,就把模式串右移到下标为2,也就是第三个字符C的位置再进行比较。注意区分此处从下标0开始存储和下标1开始存储的区别。

我的问题

编译器bug

最后讲讲我遇到的一些问题,真的痛苦,bug还没改完编译器先出问题。

“...”(Win32): 已加载......无法查找或打开 PDB 文件
“...”(Win32): 已加载“...ntdll.dll”。无法查找或打开 PDB 文件。
“...”(Win32): 已加载“...kernel32.dll”。无法查找或打开 PDB 文件
“...”(Win32): 已加载“...KernelBase.dll”。无法查找或打开 PDB 文件。
“...”(Win32): 已加载“...msvcr120d.dll”。无法查找或打开 PDB 文件
......

参考博客:无法查找或打开 PDB 文件(点击跳转)

反正是VS搞事情,见怪不怪了,习惯就好。

过大的数组长度

第二个问题是自然是,一百万个数据的处理。一开始在main函数里定义个长一百万的数组肯定不行,而且不管主串、模式串,甚至next数组的长度也要和模式串相同。

参考博客:数组元素过多怎么处理(点击跳转)

因为我用的定长顺序存储结构,所以最后将主串、模式串和next数组定义为全局变量。

还没完。

粗心的我最后发现程序还是在最大长度出错,怎么回事呢?

因为我把数组长度正正好好定义为一百万,忘了我是从下标为1开始存储的。最后给长度加了2就行了。

所以建议一开始定义数组的时候把长度都加长一些

next数组不明确

一开始看到这个问题是蒙蔽的。我把next数组定为全据变量后,报错“next 不明确”

我:???第一次看到这么神奇的bug

不过经过一番研究,发现next是C++的保留字,存在std::next的表达。所以将next作为main中的变量名是可以的,但是如果将其作为全局变量,在main函数中调用的时候就会出现不明确的提示。

参考:std::next(点击跳转)

所以我把next改成Next了。

代码

别问源码,再问BF。

不过还是给出比较难理解的部分代码梳理一下

void getNext(SString T, int next[]) {
int i = 1, j = 0; //j表示最长串的最后一个,i进行遍历
next[1] = 0;
while (i < T.length) {
if (j == 0 || T.ch[i] == T.ch[j]) { //j=0表示没有相同串,ch[i] = ch[j]表示如果相同那么没有必要右移模式串
i++;
j++;
if (T.ch[i] != T.ch[j]) { //i和j都右移后如果不同,是正常情况,直接把最长串最后一个的位置给next
next[i] = j;
}
else { //但是如果相同,就让next变为next[j],因为next[j]是之前已经得到的最长串最后一个的位置
next[i] = next[j];
}
}
else { //如果j不为0且ch[i] 与 ch[j]不等,表示之前有相同串,但是现在右移了导致之前的相同串不相同了,说明之前得出的相同串已经废了,于是重置j
j = next[j];
}
}
return;
}

这里想讲的是注释最长的但是代码很短的这个

j = next[j];

之所以能这样进行赋值,实质上是为了利用我们之前已经得到的数据(k值),避免重复计算。

KMP算法的实质其实就是基于模式串的自我重复,之所以能将模式串右移,就是比较后面重复的部分的到时候失败了,所以回到前面重复的部分。

还是这个例子,ABCABB。其中最长的重复部分是AB,那么当最后一个B不同的时候,我就移动整个模式串,让前面的AB站到后面AB的位置继续比较,从而缩减少比较次数。

那么,我已经知道AB是重复的了。那么当我比较后面的AB的B的时候发现不匹配,我就可以直接用前面的AB的B的k值,这样后面就不用在算了。

于是便有了上面这行代码。

为了大家能更好理解,我po上源码。注释也尽量写的详细了,上面的内容也是对代码的一个扩充,希望大家能理解这段代码。

#include <iostream>
#include <string.h> using namespace std; //定义从1开始存放的串结构,由Read实现
typedef struct {
char ch[1000002] ; //原为char temp[1000002] = { " " };感谢不愿透露姓名的小仙女的指正,详见评论
int length;
}SString; void Read(SString &S, char temp[]);
void getNext(SString T, int next[]);
int KMP(SString S, SString T, int next[]); SString S, T;
char temp[1000002] = { ‘ ’ }; //原为char temp[1000002] = { " " }; 同上
int Next[1000002] = { 0 }; int main() {
int result = 0; //result为结果,即T在S中第一个出现的位置,没有出现则为0
cin >> temp;
Read(S, temp);
cin >> temp;
Read(T, temp);
getNext(T, Next);
result = KMP(S, T, Next);
cout << result;
return 0;
} //先让模式串自我比较得出next数组,因为数组实际上是首地址,所以可以用void类函数且不引用
void getNext(SString T, int next[]) {
int i = 1, j = 0; //j表示最长串的最后一个,i进行遍历
next[1] = 0;
while (i < T.length) {
if (j == 0 || T.ch[i] == T.ch[j]) { //j=0表示没有相同串,ch[i] = ch[j]表示如果相同那么没有必要右移模式串
i++;
j++;
if (T.ch[i] != T.ch[j]) { //i和j都右移后如果不同,是正常情况,直接把最长串最后一个的位置给next
next[i] = j;
}
else { //但是如果相同,就让next变为next[j],因为next[j]是之前已经得到的最长串最后一个的位置
next[i] = next[j];
}
}
else { //如果j不为0且ch[i] 与 ch[j]不等,表示之前有相同串,但是现在右移了导致之前的相同串不相同了,说明之前得出的相同串已经废了,于是重置j
j = next[j];
}
}
return;
} int KMP(SString S, SString T, int next[]) { //S主串,T模式串
int i = 1, j = 1; //i指主串,j指模式串
int result = 0;
while (i <= S.length&&j <= T.length) {
if (j == 0 || S.ch[i] == T.ch[j]) { //如果匹配接着往下比较,不匹配就右移T
i++;
j++;
}
else {
j = next[j];
}
}
if (j > T.length) { //当且仅当j到最后仍与主串相等+1时匹配成功
result = i - T.length;
}
return result;
} void Read(SString &S, char temp[]) { //将输入的字符串接在S后面,实现从下标为1开始存储
S.ch[0] = ' ';
strcat(S.ch, temp);
S.length = strlen(S.ch) - 1;
return;
}

基于KMP算法的字符串模式匹配问题的更多相关文章

  1. KMP算法 (字符串的匹配)

    视频参考 对于正常的字符串模式匹配,主串长度为m,子串为n,时间复杂度会到达O(m*n),而如果用KMP算法,复杂度将会减少线型时间O(m+n). 设主串为ptr="ababaaababaa ...

  2. KMP算法(快速模式匹配)

    详细理解看这里:http://kb.cnblogs.com/page/176818/ 或者这里:http://blog.csdn.net/yutianzuijin/article/details/11 ...

  3. 回朔法/KMP算法-查找字符串

    回朔法:在字符串查找的时候最容易想到的是暴力查找,也就是回朔法.其思路是将要寻找的串的每个字符取出,然后按顺序在源串中查找,如果找到则返回true,否则源串索引向后移动一位,再重复查找,直到找到返回t ...

  4. 51NOD 1292 1277(KMP算法,字符串中的有限状态自动机)

    在前两天的CCPC网络赛中...被一发KMP题卡了住了...遂决定,哪里跌倒就在哪里爬起来...把个KMP恶补一发,连带着把AC自动机什么的也整上. 首先,介绍设定:KMP算法计划解决的基本问题是,两 ...

  5. KMP算法在字符串中的应用

    KMP算法是处理字符串匹配的一种高效算法 它首先用O(m)的时间对模板进行预处理,然后用O(n)的时间完成匹配.从渐进的意义上说,这样时间复杂度已经是最好的了,需要O(m+n)时间.对KMP的学习可以 ...

  6. KMP算法查找字符串

    假设长字符串为t,短字符串为p.为了进行KMP匹配,首先需要计算字符串p的next数组,后面实现了计算该数组的函数void KmpGenNext(char* p, int* next).对于”abca ...

  7. 字符串模式匹配KMP算法

    一篇不错的博客:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html KMP字符串模式匹配通俗点说就是一种在一个字符串中 ...

  8. KMP算法

    KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...

  9. 字符串模式匹配——KMP算法

    KMP算法匹配字符串 朴素匹配算法   字符串的模式匹配的方法刚开始是朴素匹配算法,也就是经常说的暴力匹配,说白了就是用子串去和父串一个一个匹配,从父串的第一个字符开始匹配,如果匹配到某一个失配了,就 ...

随机推荐

  1. 1040 mysql Too many connections

    笔者在项目中遇到mysql 出现:1040 too many connections 异常,意思是超过数据库最大连接数,打不开表结构信息.笔者排除问题建议:1.查看程序代码是否存在BUG:2.检查代码 ...

  2. 如何使用python在保留原excel格式的前提下插入/修改数据

    一.需求分析: 统计的报表中需要每日查询当天数据并追加到原有的excel后面. 因为原始excel格式已经设定好,如果使用xlwt,仅仅指定设定我们要插入的单元格的格式,原始数据的格式会被初始化. 所 ...

  3. 在Javascript中闭包(Closure)

    在Javascript中闭包(Closure) 什么是闭包 “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ...

  4. ssm项目整合shiro

    pom.xml <properties> <shiro.version>1.2.2</shiro.version> </properties> < ...

  5. 20164301 Exp1 PC平台逆向破解

    逆向及Bof基础实践 一.实践目标 本次实践的对象是一个名为pwn1的linux可执行文件.该程序正常执行流程是:main调用foo函数, foo函数会简单回显任何用户输入的字符串.该程序同时包含另一 ...

  6. PY序

    Python实现机器学习依赖于两个类库——SciPy和scikit-learn 一)SciPy SciPy是数学运算的基本类库,在机器学习的过程中,主要运用NumPy.Matplotlib和Panda ...

  7. 当鼠标经过表格数据行时颜色不同且奇偶行颜色也不同 (纯CSS)

    <!DOCTYPE html> <!-- To change this license header, choose License Headers in Project Prope ...

  8. 关于让simulink中display组件显示二进制的方法

    关于让simulink中display组件显示二进制的方法 1.首先需要配置gateway out模块,勾选propagate data type to output 2.配置display模块 fo ...

  9. 【CentOS】设置定时执行任务

    1.Crond服务启动状态确认 service crond status crond (pid ) を実行中... 2.追加新的执行任务 crontab -e #Ansible: dnsmasq fo ...

  10. Azkaban 使用问题及解决(一)

    什么是Azkaban Azkaban是一款基于Java编写的任务调度系统 任务调度:有四个任务脚A.B.C.D,其中任务A与任务B可以并行运行,然后任务C依赖任务A和任务B的运行结果,任务D依赖任务C ...