KMP详解

最长前后缀公共子串

一个字符串的最长前后缀公共造子串指这个字符串的前缀和后缀的最长公共子串,但不能是字符串本身。比如 a b c d a b c abcdabc abcdabc的最长前后缀公共子串为 a b c abc abc a a a a a a aaaaaa aaaaaa的最长前后缀公共子串为 a a a a a aaaaa aaaaa

KMP

给出长度为 n n n文本串A和长度为 m m m模式串B,要在A中查找B出现的位置。朴素算法的时间复杂度是 O ( n m ) O(nm) O(nm)的,效率较低。于是KMP就派上用场了。

算法流程

假设文本串A匹配到 i i i位置,模式串B匹配到 j j j位置

1.如果 j = − 1 j=-1 j=1,或字符串匹配成功(即 a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j]),则 i + + i++ i++ j + + j++ j++,继续匹配下一个字符

2.如果 j ! = − 1 j!=-1 j!=1,且当前字符串匹配失败(即 a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j]),则 i i i不变, j = n x t [ j ] j=nxt[j] j=nxt[j]。即失配时,B相对于A向右移动了 j − n x t [ j ] j-nxt[j] jnxt[j]

n x t nxt nxt数组的意义在于当模式串失配时, n x t nxt nxt值告诉你在下一步匹配时跳到哪个位置。

如果字符串是 a b a a abaa abaa

模式串abab
最大前缀后缀公共子串长度0012
模式串abab
next数组-1001

那么如何求 n x t nxt nxt数组呢?

我们可以用递推的方法,假设 n x t nxt nxt数组中0到i-1都以求出,现在求 n x t [ i ] nxt[i] nxt[i]

如果 s [ i − 1 ] = = s [ n x t [ i − 1 ] ] s[i-1]==s[nxt[i-1]] s[i1]==s[nxt[i1]],则 n x t [ i ] = n x t [ i − 1 ] nxt[i]=nxt[i-1] nxt[i]=nxt[i1]

否则,继续判断 s [ i − 1 ] s[i-1] s[i1]是否等于 n x t [ n x t [ i − 1 ] ] nxt[nxt[i-1]] nxt[nxt[i1]]

如果相等,则 s [ i ] = n x t [ n x t [ i − 1 ] ] + 1 s[i]=nxt[nxt[i-1]]+1 s[i]=nxt[nxt[i1]]+1

否则继续判断,直到相等或 n x t nxt nxt值为 − 1 -1 1为止。

时间复杂度为 O ( n + m ) O(n+m) O(n+m)

code

char s[N];
void gt(){
    nxt[0]=-1;
    int len=strlen(s);
    for(int i=1;i<len;i++){
        int j=nxt[i-1];
        while(j!=-1&&s[j]!=s[i-1]) j=nxt[j];
        nxt[i]=j+1;
    }
}

接下来就是文本串和模式串的匹配。

code

char s1[N],s2[N];
int kmp(){
    inr l1=strlen(s1),l2=strlen(s2);
    if(l1<l2) return -1;
    gt(s2);
    for(int i=0,j=0;i<=l1;i++,j++){
        if(j==l2) return i-j;
        if(i==l1) return -1;
        while(j!=-1&&s1[i]!=s2[j]) j=nxt[j];
    }
}

例题

洛谷P3375 【模板】KMP字符串匹配

code

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int ans,nxt[1000005],ex[1000005];
char s1[1000005],s2[1000005];
void getnxt(){
    nxt[0]=-1;
    int l=strlen(s2);
    for(int i=1;i<l;i++){
        int j=nxt[i-1];
        while(j!=-1&&s2[j]!=s2[i-1]) j=nxt[j];
        nxt[i]=j+1;
    }
}
int KMP(){
    int l1=strlen(s1),l2=strlen(s2);
    if(l1<l2) return -1;
    getnxt();
    for(int i=0,j=0;i<=l1&&j<=l2;i++,j++){
        if(i==l1) return -1;
        if(j==l2-1&&s1[i]==s2[j]) printf("%d\n",i-j+1),j=nxt[j];
        while(j!=-1&&s1[i]!=s2[j]) j=nxt[j];
    }
    return -1;
}
int main()
{
    scanf("%s%s",s1,s2);
    KMP();
    int l=strlen(s2);
    for(int i=0;i<l;i++){
        int ans=nxt[i];
        while(ans!=-1&&s2[ans]!=s2[i]) ans=nxt[ans];
        printf("%d ",ans+1);
    }
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值