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

时间:2022-06-05 02:48:45

基于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. 回朔法&sol;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. iOS 实现类似雷达效果的核心代码

    -(void)drawRect:(CGRect)rect { [[UIColor clearColor]setFill]; UIRectFill(rect); NSInteger pulsingCou ...

  2. TCP的关闭,到底是几次握手,每次的标志位到底是什么!

    做题的时候遇到一个问题,TCP关闭的时候到底是三次还是四次握手,如果是三次,少了哪部分?   按照 <计算机网络> -第五版-谢希仁       然而对于TCP关闭, 有的地方能找到   ...

  3. 【转】ini载入保存类&comma;操作INI配置文件方便的很

    /****************************************************************** * * ^_^ 恶猫 独门商标 挖哈哈 * * QQ:\> ...

  4. c&num;获取今天星期几

    System.Globalization.CultureInfo.CurrentCulture.DateTimeFormat.GetDayName(DateTime.Now.DayOfWeek)

  5. Ubuntu下安装配置zsh和oh my zsh

    zsh优势:自动补全功能强大和很高的可配置性 1.查看当前系统装了哪些shell    cat /etc/shells 2.当前正在运行的是哪个版本的shell    echo $SHELL 3.安装 ...

  6. Eclipse中点击小猫提示Tomcat settings should be set in Tomcat Preference Page

    1.window->preference->tomcat->tomcat-version选择自己tomcat版本 tomcat home 选择tomcat安装目录,即bin的上一层 ...

  7. Asp&period;net Mvc 请求是如何到达 MvcHandler的——UrlRoutingModule、MvcRouteHandler分析,并造个*

    这个是转载自:http://www.cnblogs.com/keyindex/archive/2012/08/11/2634005.html(那个比较容易忘记,希望博主不要生气的) 前言 本文假定读者 ...

  8. JavaScript 进阶(一)JS的&quot&semi;多线程&quot&semi;

    这个系列的文章名为“JavaScript 进阶”,内容涉及JS中容易忽略但是很有用的,偏JS底层的,以及复杂项目中的JS的实践.主要来源于我几年的开发过程中遇到的问题.小弟第一次写博客,写的不好的地方 ...

  9. Bitmap 格式

    源:Bitmap 格式 参考:bitmap文件格式 Bitmap是Windows操作系统中的标准图像文件格式,可以分成两类:设备相关位图(DDB)和设备无关位图(DIB),DDB已经基本停用. Bit ...

  10. webForm系列 前端框架快速引用

    Html的确定就是不能重用,MVC可以在_Layout.cshtml中将每个页面都需要的js和css文件(如jq,bootstrap等)都引用进去,webform就麻烦一点. webForm需要给所以 ...