拓展kmp(2020新年第一篇博客 学无止境冲啊)

拓展kmp算法可解决以下问题,给定两个字符串s1和s2,可以求出s1的extend数组(简记为ex),其中extend[i]表示s1从第i位开始的子串和s2的最大公共前缀长度,举个例子:可以看到extend[0]=4最长公共前缀是aaaa (s1=“aaaaaaa”,s2=“aaaa”)
在这里插入图片描述
一:暴力计算ex数组。
我们枚举s1的每一个位置,每次都从s2的开头开始匹配,直到失配为止。比如上图求解ex[0]时从0开始直到到达s2的最大长度失配,所以长度为s1;
但是暴力就有很多重复的比较,也就是已知的信息没有利用起来,如何利用起来呢?:当我们知道ex[0]=4的时候,我们就可以得到s1[0…3]=s2[0…3],即s1[1…3]=s2[1…3],我们引入一个针对于s2的next数组,next[i]=k表示 s2[0…k-1]=s2[i…i+k-1],如下图,我们看到next[1]=3,则说明s2[0…2]=s2[1…3],s2[1…3]=s1[1…3],得到s1[1…3]=s2[0…2],所以我们在求ex[1]=的时候根本没必要从s1[1]开始匹配,可以直接从s1[4]开始匹配直到失配为止。
在这里插入图片描述
至此,我们引入关于s2的next数组进行辅助求解s1的ex数组,next数组的几何含义差不多像这样.红色部分代表这部分相等
在这里插入图片描述
二.用辅助next数组来进行拓展kmp求解ex数组
这实际上是一个递推的过程,我们现在假设要求ex[i],为进行递推,我们需要知道一些条件,对于每个下标k(0=<k<i) 都已知了ex[k],由这个我们可以得到每个k的匹配区间[st,ed] 其中st=i,ed=i+ex[i]-1,需要的条件便是这个最大的st和其对应的匹配区间[st,ed]的长度ed+1-st,现在我们开始求i,
在这里插入图片描述
我们可以得到s2[0 … ed-st]=s1[st … ed],所以也就有
s2[i-st … ed-st]=s1[i … ed]
在这里插入图片描述
假设next[i-st]=len; 所以就有s2[i-st … i-st+len-1]=s2[0 … len-1].
很明显这里我们要根据i+len-1与ed的位置关系来讨论才能求解ex[i];如下图所示,相同颜色的部分代表这一区间相匹配
在这里插入图片描述
第一种情况:i+len-1==ed,此时我们的位置就好像这样,不用说 ,由于不清楚ed后一位的匹配情况,因此我们要从s2[len]和s1[i+len]继续往后匹配,len=ed+1-i;,这时所有的图中匹配区间长度都相等
在这里插入图片描述
第二种情况:i+len-1>ed ,和第一种情况一样 也不知道ed后面的情况所以还要从s1的ed后开始匹配
在这里插入图片描述
第三种情况:i+len-1<ed时,那么我们就可以确定其匹配长度就是len,因为假设们匹配长度为len+1,那么就得到了s1[i … i+len]=s2[0 … len],而我们的s1[i … i+len]=s2[i-st … i-st+len ] ,从而推出
s2[0 … len]=s2[i-st … i-st+len ],即next[i-st]=len+1; 和我们的假设冲突。
在这里插入图片描述
所以我们就求解出了s1的ex数组,而s2的next数组的求法,其实本质和s1的ex数组求法一致,我们可以把s2看作母串,s2的每个后缀看为新的子串,那么就相当于求解母串的ex数组,而我们求第i个时又只需要知道i-1之前的信息,这样就可以把ex数组求出来,这就是s2的next数组。
例题:hdu2328
AC代码

#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<time.h>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<functional>
#include<stack>
#include<map>
#include<queue>
#define mod (1000)
#define middle (l+r)>>1
#define SIZE 10000000+10
#define lowbit(x) (x&(-x))
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long ll;
typedef long double lb;
const int inf_max = 0x3f3f3f3f;
const ll Linf = 9e18;
const int maxn = 4000+10;
const long double e = 2.7182818;
const double eps=0.0001;
using namespace std;
int n;
char str[maxn][200+10],ex[210],nnext[210];
void mycopy(char ch[],int l,int r,char soc[])
{
    int cnt=0;
    for(int i=l;i<=r;i++)
        ch[cnt++]=soc[i];
    ch[cnt]='\0';
}
void exkmp_pre(char *s)
{
    int len=strlen(s);
    nnext[0]=len;
    int j=0,i=1;
    while(j+1<len&&s[j]==s[j+1]) ++j;
    nnext[i++]=j;
    int st=1;
    while(i<len) {
        int ed=st+nnext[st]-1;
        int L=nnext[i-st];   //L表示从母串i位置开始,子串0位置开始已经匹配好了的长度
        if(i+L<ed+1) nnext[i]=L;
        else {
            j=max(0,ed-i+1);
            while(j+i<len&&s[j+i]==s[j]) ++j;
            nnext[i]=j;st=i;
        }
        i++;
    }
}
bool exkmp(char *s1,char *s2)   //s1作为母串进行exkmp
{
    memset(ex,0,sizeof(ex));
    int len1=strlen(s1),len2=strlen(s2);
    int j=0;
    while(j<len2&&j<len1&&s1[j]==s2[j]) ++j;
    ex[0]=j;
    if(j==len2) return true;
    //printf("j=%d\n",j);
    int st=0;
    for(int i=1;i<len1;i++) {
        int ed = ex[st]+st-1;
        int L = nnext[i-st];
        if(i+L < ed+1) ex[i]=L;   //为什么是ed+1?因为如果等于i+L-1==ed时下标为ed的后一位就可能继续匹配了,所以这时候ex[i]有两种可能 ex[i]==ed||ex[i] > ed;所以要归到下种case判断
        else {
            j=max(0,ed+1-i);
            while(i+j<len1&&j<len2&&s1[i+j]==s2[j]) ++j;
            ex[i]=j;
            if(ex[i]==len2) return true;
            st=i;
        }
    }
    return false;
}
char ans[210];
int main()
{
    char ch1[210]="abab",ch2[210]="babab";
    exkmp_pre(ch1);
    exkmp(ch2,ch1);
    while(scanf("%d",&n),n)
    {
        ans[0]='0';ans[1]='\0';
        int minv=inf_max,pos;
        for(int i=1;i<=n;i++) {
            scanf("%s",str[i]);
            if(minv>strlen(str[i])) {minv=strlen(str[i]);pos=i;}
        }
        int Max=0;

        for(int i=0;i<strlen(str[pos]);i++) {
            for(int j=i;j<strlen(str[pos]);j++) {
                memset(nnext,0,sizeof(nnext));
                char son[210];
                mycopy(son,i,j,str[pos]);
                bool flag=true;
                if(strlen(son)<Max) continue;  //剪枝
                exkmp_pre(son);
                for(int k=1;k<=n;k++) {
                    if(k==pos) continue;
                    if(!exkmp(str[k],son)) {flag=false;break;}  //如果有一个字符串不包含这个字串 那么这个不是可能的答案
                }
                if(flag) {  //如果这个子串是一个可能的答案
                    //printf("ans=%s\n",ans);
                    int now=j+1-i;
                    if(now==Max) { //保留字典序最小的
                        if(strcmp(ans,"0")==0||strcmp(ans,son)>0) strcpy(ans,son);
                    }
                    else if(now>Max) {
                        Max=now;
                        strcpy(ans,son);
                    }
                }

            }
        }
        if(Max==0) printf("IDENTITY LOST\n");
        else printf("%s\n",ans);
    }
    return 0;
}

参考:
https://blog.csdn.net/qq_40160605/article/details/80407554
https://blog.csdn.net/dyx404514/article/details/41831947

发布了33 篇原创文章 · 获赞 14 · 访问量 418

猜你喜欢

转载自blog.csdn.net/qq_44077455/article/details/103789951