基于KMP算法的字符串模式匹配问题
基于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为例)
- 把模式串的最后一个字符遮住,剩下的串是不是能分成很多子串?值得注意的是,这里的字串要包括未遮住部分的第一个字符,如遮住最后一个B,剩下的是ABCAB,从左到右看依次是A,AB,ABC,ABCA,ABCAB,他们就是后前缀
- 把模式串的第一个字符遮住,剩下的串是不是能分成很多子串?同样,这里的字串要包括未遮住部分的第一个字符,如遮住第一个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算法的字符串模式匹配问题的更多相关文章
- KMP算法 (字符串的匹配)
视频参考 对于正常的字符串模式匹配,主串长度为m,子串为n,时间复杂度会到达O(m*n),而如果用KMP算法,复杂度将会减少线型时间O(m+n). 设主串为ptr="ababaaababaa ...
- KMP算法(快速模式匹配)
详细理解看这里:http://kb.cnblogs.com/page/176818/ 或者这里:http://blog.csdn.net/yutianzuijin/article/details/11 ...
- 回朔法/KMP算法-查找字符串
回朔法:在字符串查找的时候最容易想到的是暴力查找,也就是回朔法.其思路是将要寻找的串的每个字符取出,然后按顺序在源串中查找,如果找到则返回true,否则源串索引向后移动一位,再重复查找,直到找到返回t ...
- 51NOD 1292 1277(KMP算法,字符串中的有限状态自动机)
在前两天的CCPC网络赛中...被一发KMP题卡了住了...遂决定,哪里跌倒就在哪里爬起来...把个KMP恶补一发,连带着把AC自动机什么的也整上. 首先,介绍设定:KMP算法计划解决的基本问题是,两 ...
- KMP算法在字符串中的应用
KMP算法是处理字符串匹配的一种高效算法 它首先用O(m)的时间对模板进行预处理,然后用O(n)的时间完成匹配.从渐进的意义上说,这样时间复杂度已经是最好的了,需要O(m+n)时间.对KMP的学习可以 ...
- KMP算法查找字符串
假设长字符串为t,短字符串为p.为了进行KMP匹配,首先需要计算字符串p的next数组,后面实现了计算该数组的函数void KmpGenNext(char* p, int* next).对于”abca ...
- 字符串模式匹配KMP算法
一篇不错的博客:http://www.cnblogs.com/dolphin0520/archive/2011/08/24/2151846.html KMP字符串模式匹配通俗点说就是一种在一个字符串中 ...
- KMP算法
KMP算法是字符串模式匹配当中最经典的算法,原来大二学数据结构的有讲,但是当时只是记住了原理,但不知道代码实现,今天终于是完成了KMP的代码实现.原理KMP的原理其实很简单,给定一个字符串和一个模式串 ...
- 字符串模式匹配——KMP算法
KMP算法匹配字符串 朴素匹配算法 字符串的模式匹配的方法刚开始是朴素匹配算法,也就是经常说的暴力匹配,说白了就是用子串去和父串一个一个匹配,从父串的第一个字符开始匹配,如果匹配到某一个失配了,就 ...
随机推荐
- 实验十 ZStack 网状网络实验
实验十 ZStack 网状网络实验[实验目的]1. 了解 ZigBee 网状网络结构2. 掌握构建网状网络的方法[实验设备]1. 装有 IAR 开发工具的 PC 机一台2. 实验箱一台3. CCDeb ...
- day11.2lambda函数表达式 及其返回值注意事项
lambda表达式 用于表示简单的函数 func1=lambda 参数:一句话函数体(默认返回函数体内执行的内容) 1.1结构 func=lambda a1,a2 : a1+100 val=func( ...
- useful urls
数据挖掘技术: http://ddl.escience.cn/f/IwoF?rid=8188575 李航 统计学习方法: http://ddl.escience.cn/f/Iwn0
- Wpf TemplateBinding
TemplateBinding. ControlTemplate最终会被用到一个控件上,我们称这个控件为模板目标控件或者模板化控件,ControlTemplate里面的控件可以使用TemplateBi ...
- for和for in区别
for ... in 循环中的代买每执行一次,就会对数组的元素或者对象的属性进行一次循环操作. eg:应该用在非数组对象的遍历上,使用for-in进行循环也被称为“枚举”. for (变量 in 对象 ...
- 剖析servlet injection及源码分析.
@WebServlet("/cdiservlet") public class NewServlet extends HttpServlet { private Message m ...
- JavaScript原型(第五天)
避免对象重复使用,有时候js中会用到原型 function Person(){ name="test"; age=123; } var car={ price=10000; } P ...
- 名字top500字典 各种格式及python脚本
原文件名字top500 链接: https://pan.baidu.com/s/1cv0jPYb1-EBceoZz3QNvgg 密码: bat5 中文名字 链接: https://pan.baidu. ...
- java8_api_stream
与集合联系紧密 Stream-1 stream概念 特点 使用示例
- css多行省略
单行省略就不用说了,用css实现非常简单,兼容性还非常好.但是多行省略一直都是前端的痛点,在css3之前,可以用js去算两行能放多少个字,把多余的字用 ... 代替,且不说好不好,万一哪天PM说要改成 ...