【黑马计划-1】KMP及扩展KMP

本文深入讲解了KMP算法的核心概念及其实现细节,包括fail数组的定义及其在提高字符串匹配效率方面的作用,并进一步介绍了扩展KMP算法的概念及其实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

KMP

核心

玄学 failfail 数组
failfail 数组的含义即是某段字符串的最长公共前后缀。
具体来讲,设 T[1j1]=T[iji1](ji)T[1…j−1]=T[i−j…i−1](j⩽i) ,那么 fail[i]fail[i]jj 的最大值。

那么这个 fail 有什么用呢?见下图。
发生失配时,当前指针从 $i$ 跳到 $fail[i]$
匹配时,设当前匹配到 SS 的第 i 位, TT 的第 j 位,即 S[ij+1i1]S[i−j+1…i−1]T[1j1]T[1…j−1] 已成功匹配。当 jj 指针这一位发生失配,意味着 S[i]!=T[j] ,这时根据 failfail 数组的定义,由于 T[1fail[j]1]=T[jfail[j]j1]T[1…fail[j]−1]=T[j−fail[j]…j−1] ,因此若将 jj 指针指向 fail[j] ,我们可以直接跳过对 T[1fail[j]1]T[1…fail[j]−1] 的匹配。

复杂度证明

nn 为串 S 的长度, mm 为串 T 的长度。
ii 指针全程只增,这里的复杂度为 O(n)
jj 指针全程只有两种跳法: jj+1jfail[j]j→fail[j]
对于 jj+1j→j+1 全程最多跳 nn 次。
对于 jfail[j]
ii 保持不变的情况下, j 跳至下界的极限次数一定不超过 jj (根据 fail 的定义)。因此设 f[j]f[j] 表示 jj 这个位置发生失配跳至下界的上限次数, g[j] 为跳完 f[j]f[j] 次之后 jj 的位置。而 j 每跳一次, g[j]g[j] 一定减小至少 11f[j] 随之减小至少 11 ,从而最终跳的次数上界为 U=max{f[j]} 。由 ff 的定义我们知道, max{f[x]}=n ,故最终 jj 跳的次数一定不超过 n
又由于要单独对串 TT 单独求一次 fail ,复杂度证明同上,为 O(m)O(m)
综上,由于 ii 全程迭代 n 次, jj 全程迭代不超过 n 次,故时间复杂度为 O(n+m)O(n+m)

扩展KMP

“扩展”

引入 extext 数组, ext[i]ext[i] 表示 S[in]S[i…n]T[1m]T[1…m] 的最长公共前缀( nn 为串 S 的长度, mm 为串 T 的长度)。
考虑如何求 extext

引入辅助工具

设当前需要计算 ext[i]ext[i] 的值, ppext[j] 最大时 jj 的值 (1j<i)
就有 S[pp+ext[p]1]=T[1ext[p]]S[p…p+ext[p]−1]=T[1…ext[p]]
于是 S[ip]=T[ext[p]p+iext[p]]S[i…p]=T[ext[p]−p+i…ext[p]]
这时求 extext 就有两种情况:
1、 i+fail[i]<p+ext[p]i+fail[i]<p+ext[p]
由于 S[ip]=T[ext[p]p+iext[p]]S[i…p]=T[ext[p]−p+i…ext[p]]
又由 failfail 的定义知 S[ii+fail[ip+1]1]=T[1fail[ip+1]]S[i…i+fail[i−p+1]−1]=T[1…fail[i−p+1]]fail[ip+1]fail[i−p+1] 为最大匹配长度,故 ext[i]=fail[ip+1]ext[i]=fail[i−p+1]
 $i+fail[i]<p+ext[p]$ ,红色两段完全相同

2、 i+fail[i]p+ext[p]i+fail[i]≥p+ext[p]
此处求法与 failfail 求法几乎一样,可参考 failfail 的求值。

复杂度证明:对于情况1,单次复杂度为 O(1)O(1) ;对于情况2,总复杂度与KMP算法中一致,为 O(n+m)O(n+m)

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

const int LENGTH=1000000;
char S[LENGTH+2],T[LENGTH+2];

namespace KMP{
    int fail[LENGTH+2];
    int cnt[LENGTH+2],ext[LENGTH+2];
    void get_fail(char *t){
        int len=strlen(t+1);
        for(int i=2,j=0;i<=len;++i){
            while(j&&t[i]!=t[j+1])j=fail[j];
            if(t[i]==t[j+1])fail[i]=++j;
        }
    }
    void KMP(char *s,char *t){
        get_fail(t);
        int s_len=strlen(s+1),t_len=strlen(t+1);
        for(int i=1,j=0;i<=s_len;++i){
            while(j&&s[i]!=t[j+1])j=fail[j];
            if(s[i]==t[j+1]){
                cnt[i]=++j;
                if(j==t_len)j=fail[j];
            }
        }
    }
    void ex_KMP(char *s,char *t){
        get_fail(t);
        int s_len=strlen(s+1),t_len=strlen(t+1);
        int p=1;
        while(ext[1]<t_len&&s[ext[1]+1]==t[ext[1]+1])++ext[1];
        for(int i=2;i<=s_len;++i){
            if(i+fail[i-p+1]<p+ext[p])ext[i]=fail[i-p+1];
            else{
                int j=ext[p]+p-i;
                if(j<0)j=0;
                while(i+j<=s_len&&j<t_len&&s[i+j]==t[j+1])++j;
                ext[i]=j;
                p=i;
            }
        }
    }
}

int main(){
    scanf("%s%s",S+1,T+1);
    KMP::KMP(S,T);
    KMP::ex_KMP(S,T);
    int s_len=strlen(S+1),t_len=strlen(T+1);
    printf("Array of fail:\n\t");
    for(int i=1;i<=t_len;++i)printf("%d ",KMP::fail[i]);
    printf("\nMatching position:\n\t");
    for(int i=1;i<=s_len;++i)if(KMP::cnt[i]==t_len)printf("%d ",i-t_len+1);
    printf("\nArray of extend:\n\t");
    for(int i=1;i<=s_len;++i)printf("%d ",KMP::ext[i]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值