KMP是啥?
一种匹配算法,能够在线性时间内判定一个字符串A是否是字符串B的子串(即连续的字符),以及出现的次数。
我们由例题引入:
给定一个文本串S和一个模式串T,问模式串在文本串中出现了多少次?
首先,一个暴力的做法是枚举S中的每一位,向后与T串每一位进行匹配,匹配失败就移到下一位。复杂度是O(n*m)的
当然hash也可以在xian线性时间内解决这道题目,但KMP能更高效地处理这个问题,bing并且neng能给我们提供一些额外的信息。
先看我们是如何写暴力的,每一次都匹配一次串T的长度,可以很容易就看出我们有大量重复的匹配。
考虑如何减少重复的匹配:
如果当前模式串的T[1..x]已经跟文本串的S[p+1..p+x]完全匹配,但是T[x+1]与S[p+x+1]无法匹配。那么此时我们考虑如何运用已有的T[1..x]和S[p+1..p+x]完全匹配的信息。
考虑如果T[1..x]这个字符串如果某个后缀T[x-k..x] 跟前缀T[1..1+k]可以完全匹配,那么此时T[1..1+k]就可以和S[p+x-k..p+x]完全匹配。
我们需要找到与最长的能跟前缀完全匹配的后缀这样我们就能在匹配失败以后,就可以直接将模式串开头移动到S[p+x-k]的位置。用next[i]表示以i为结束的后缀能够匹配的最长的前缀。
考虑在next[1]…next[i-1]都计算出来的情况下如何计算next[i]。
如果next[i-1]的下一位能和s[i]匹配,那么next[i]=next[i-1]+1。
不能匹配呢?
如果不能,那么我们需要找到next[next[i-1]]判断他的下一位能否和s[i]匹配。如果还不能,就继续往前找,直到找到0为止。
KMP复杂度分析:
考虑一下计算next数组的过程。每次向后匹配一个字符的时候,当前的next指针都至多+1,所以考虑从开始计算到最后的全部过程中,当前的next指针最多增加了n次,所以next指针向前跳的次数也至多为n次。所以计算next数组的时间复杂度是O(n)。匹配的复杂度证明与求next数组类似。时间复杂度为O(m)。
模版代码:
int now=0;
nxt[1]=0;//next数组
for(int i=2;i<=n;i++)
{
while(now&&s[now+1]!=s[i]) now=nxt[now];
if (s[now+1]==s[i]) now++;
nxt[i]=now;
}//求next数组
now=0;
for(int i=1;i<=m;i++)
{
while(now&&s[now+1]!=a[i]) now=nxt[now];
if (s[now+1]==a[i]) now++;
if (now==n)
{
ans++;
now=nxt[now];
}
}//KMP匹配