【给数学不好的人的KMP】字符匹配教程(一)next数组的理解

时间:2022-02-03 06:32:16

    终于接触了一丁点字符匹配的东西(其实也只是入门的KMP),话说这个东西的确需要很强的数学思维,其中NEXT数组尤其坑爹,很多ACMER就是栽在了这个数组上,不明觉厉。什么诸如“失配” “数组移位”和一些S(i-1)...之类的一长串字母的确让我们十分不爽。更有博客表示这个确实很难,此中有真意,欲辨已忘言云云。。算法导论上的模板是从1开始的,自己还写了个SString数据类型让好多人云里雾里,今天我们就来试着解释一下KMP的基本原理吧。

    字符匹配的基本问题是寻找子串在一长串中的位置。比如

一长串s:abacababt

      子串t:abab,返回值就是4.当然你要非说5也行。。

寻找这种东西的位置,最先想到的思路自然是暴搜。。我们用i来表示大串中当前查找的位置,j表示子串中当前查找的位置,开始都是0,然后开搜,条件很简单,s[i]==t[j]。。。

原理见图,【给数学不好的人的KMP】字符匹配教程(一)next数组的理解

代码如下:

//KMP的前奏——BF 
//O(n)=m*n
#include <iostream>
#include <string>
using namespace std;
int BF_Find(string s,string t,int pos) //pos从0开始,输出绝对位置要加1
{
int i=pos;
int j=0;
while(i<s.size() && j<t.size())
{
if(s[i]==t[j])
{
i++;
j++;
}
else
{
i=i-j+1;//一个一个的搜
j=0;
}
}
if(j>=t.size()) //目标串匹配结束
{
return i-t.size();
}
else
return -1;

}

int main()
{
string need,tar;
int pos;
cin>>need>>tar>>pos;
cout<<BF_Find(need,tar,pos)+1<<endl;//输出绝对位置
return 0;
}
喜闻乐见的是,这个方法时间复杂度高,达o(M*N),一会会就TLE了,于是KMP正式登场,它的思想是预处理,解决了每匹配失败一次,I就无脑后移J清零的问题。(时间主要就浪费在这上边了)预处理的方式就是这个坑爹的数组——next[]。

网上的说法好多都是用的数学推理,当时就ORZ了。。

next数组的本质其实就是如图下!!简要介绍下。

next[n],其实就是求从前往后数的字符串和从后往前数的字符串完全对应的最长位数(从后数和从前数不能完全相同),举例如a[5]=ababbc,

next[0]=0(也可以是-1,网上的教程表示无所谓)

next[1]=0,这个串(ab)从前缀数有a,ab,从后缀数有b,ab……不能相同,所以没一样的,为0.

next[2]=1,这个串(aba)从前缀数有a,ab,aba,从后缀数有a,ba,aba……不能相同(去掉aba和aba),还剩下a[0]即a与a[3]即a完全对应,为1.

next[3]=2,这个串(abab)从前缀数有a,ab,aba,abab从后缀数有b,ab,bab,abab……不能相同(去掉abab和abab),还剩下ab和ab完全对应,为2.

next[4]=0,这个串(abab)从前缀数有a,ab,aba,abab,ababb从后缀数有b,bb,abb,babb,ababb……不能相同(去掉ababb和ababb),没一样的,为0.

next[5]=0,原理同上。

【给数学不好的人的KMP】字符匹配教程(一)next数组的理解

这个数组可以使用搜索的方法得到。网上的教程质量参差不齐,这里给出一个能用的。下标从0开始,next[0]默认为0,获得的数组可以使用调试功能查看。

int next[1000];

void build_next(string b)
{
int i,j=0,key=0;
memset(next,0,sizeof(next));
for(i=1;i<b.size();i++)
{
j=next[i-1];
while(j==1 && b[i]!=b[j])
{
j=next[j-1];
}
if(b[j]==b[i])
{
next[i]=j+1;
}
else
next[i]=0;
}
}

这个数组的理解与否是掌握KMP的关键,具体怎么使用,下篇文章会给出更新。