拓展kmp笔记

EXKMP解决的问题

定义母串S和子串T,S的长度为n,T的长度m。
求字符串T和字符串S的每一个后缀的最长公共前缀,这就是EXKMP解决的问题,保存这个数据的数组就是extend数组,而exkmp就是求出extend数组。
(如果extend[i] = m,则表示T串为S的子串之一,这也就是标准的kmp问题,所以称其为拓展kmp)。

与kmp区别

虽然叫做拓展kmp,并且和kmp一样都有next数组,但二者实际上并没有太大的关系。kmp的next[i] 表示p串中 0~i-1这个前缀的最长相同前后缀,例如abcabc的next[3]也就是abc这个前缀的最长相同的前后缀。而exkmp的next[i] 表示的是字串T[i, m-1]和T的最长公共前缀长度。

extend求法

首先举一个例子 S = “aaaabaa”, T = “aaaaa”。
计算extend[0]的时候,进行五次匹配,一直到失配后停止。如图(后缀包括串本身,别懵啊,要时刻记住exkmp是求S的所有后缀和T的最长公共前缀)
在这里插入图片描述
得到extend[0] = 4, 然后计算extend[1],那么计算extend[1]的时候我们肯定不能像求extend[0]那样去一点一点的匹配,那就和暴力没什么区别了。
我们知道extend[0] = 4, 容易知道S[0, 3] == T[0, 3],在进一步就能得到S[1, 3] == T[1, 3]。而计算extend[1]是从字符串1开始的,现在我们至少已经知道了S[1, 3]和T[1, 3]是相等的,通过上面我们又知道next数组的含义,现在的next[1]等于4,表明了T[0, 3]==T[1, 4], 那也就是说T[0, 2] = =T[1, 3],回到上面加粗的地方我们就知道了,S[1, 3] == T[0, 2],这样的话S[1, 3]和T[0, 2]是不需要再次匹配,直接往下匹配S[4] 和 T[3]就行。

那上面一定都要继续往下匹配吗?答案是否定的,在这里可以分成了两种情况。为什么请继续往下看。
如果某一刻extend[0, k]都已经计算完了,现在需要计算extend[k+1], 并且在extend[0, k]的最大值的坐标为po, 到达最远的距离为p。如下图,
在这里插入图片描述
计算extend[k+1],根据上面推论可知S[Po, p] = T[0, P-Po],因此S[k+1, p] = T[K-Po+1, P-Po],令len = next[K-Po+1](next[i]表示T[i,m-1]和T的最长公共前缀长度,请牢牢地记住它的含义,这对理解后面很有用处) 分为下面两种情况

1),k+len < p
如下图
在这里插入图片描述
这种情况不需要任何匹配,仔细想一下就能知道extend[k+len+1]一定不等于Tp[len+1],因为如果二者相等了,说明刚才的next[K-Po+1]一定是等于len+1, 这就形成与已知相悖。因此extend[k+1]直接能够赋值为len。(有一点绕,一定要对着图好好想一想。而且要记住是从Po到P和k没什么关系)。

2),k+len>=p
如下图,
在这里插入图片描述
通过图片我们可以知道S[k+1, P] == T[0, p-k]是相等的,但是S[P+1]的情况是未知的,因此还要从S[P+1]和T[p-k+1]开始一点一的匹配,知道失配位置,这个时候的最远距离P已经改变,一定要更新P和Po,好为后面计算做铺垫。

next数组求法

next[i]表示T[i,m-1]和T的最长公共前缀长度。根据它的定义,我们发现它和extend数组没有什么区别啊,这里只不过是S和T是一个串,那这里说明了求两个数组用的是一种方法, 不过有一点小小的不同。在代码里看吧。

代码样例

#include <iostream>
#include <cstring> 

using namespace std;

const int MAX=100010; //字符串长度最大值
int Next[MAX],extend[MAX];

//预处理计算Next数组
void getNext(char str[])
{
    int k=0,j,po,len=strlen(str);
    
    Next[0]=len; //初始化next[0]
    
    while(str[k]==str[k+1] && k+1<len) k++; 

	Next[1]=k; //计算next[1]
    po=1; //初始化po的位置
    
    for(k=2;k<len;k++)
    {
        if(Next[k-po]+k < Next[po]+po) //第一种情况,可以直接得到next[k]的值
            Next[k]=Next[k-po];
        else //第二种情况,要继续匹配才能得到next[k]的值
        {
            j = Next[po]+po-k;
            if(j<0) j=0; //如果i>po+next[po],则要从头开始匹配
            while(k+j<len && str[j]==str[j+k]) j++; Next[k]=j;
            po=k; //更新po的位置
        }
    }
}

//计算extend数组
void EXKMP(char s1[],char s2[])
{
    int k=0,j,po,len=strlen(s1),l2=strlen(s2);
    getNext(s2); //计算子串的next数组
    while(s1[k]==s2[k] && k<l2 && k<len) k++; 

	extend[0]=k;
    po=0; //初始化po的位置
    
    for(k=1;k<len;k++)
    {
        if(Next[k-po]+k < extend[po]+po) //第一种情况,直接可以得到extend[k]的值
            extend[k]=Next[k-po];
        else //第二种情况,要继续匹配才能得到extend[k]的值
        {
            j = extend[po]+po-k;
            if(j<0) j=0; //如果k>extend[po]+po则要从头开始匹配
            while(k+j<len && j<l2 && s1[j+k]==s2[j]) j++; extend[k]=j;
            po=k; //更新po的位置
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值