从0开始的字符串学习--KMP与失配树

-1、序

(我也不知道为什么一篇瞎写的学习笔记会有序qwq)

一开始学字符串的时候大多算法都是只记了下流程,并没有深入理解,于是准备重新整理一下字符串相关的一些东西,就有了这篇Blog。

0、符号及约定

(以下定义仅适用于本篇Blog)

$varepsilon$:空串,即不含任何字符的字符串;

$S, T, ldots$:字符串(若不做特殊说明,下文中大写字母均表示字符串);

$|S|$:$S$的长度;

$S_i$:$S$的第$i$个字符(从$1$开始标号,即$i in [1, |S|]$);

$S_{l ldots r}$:由$S_l, S_{l+1}, ldots, S_r$组成的子串;

$prefix(S)$:$S$的前缀集,即${S_{1 ldots i} | i in [1, |S|]} cup {varepsilon}$;

$suffix(S)$:$S$的后缀集,即${S_{i ldots |S|} | i in [1, |S|]} cup {varepsilon}$;

$T subseteq S$:$T$是$S$的子串;

$T subsetneqq S$:$T$是$S$的真子串,即$T subseteq S land T eq S$;

$ABCldots$:由若干个字符串依次首尾相接形成的新字符串;

1、Border,周期与失配树

1.1、Border

我们称$T$为$S$的Border,当且仅当:$T subsetneqq S land T in prefix(S) land T in suffix(S)$。记$Border(S)$为$S$的所有Border组成的集合。

于是就有了几个显而易见的性质:

性质1.1.1:$varepsilon$是任何非空字符串的Border。

性质1.1.2:$S_{1 ldots t} in Border(S) longleftrightarrow S_{1 ldots t} = S_{|S|-t+1 ldots |S|}$。

性质1.1.3(Border的传递性):若$P$是$Q$的Border且$Q$是$S$的Border,那么$P$是$S$的Border。

证明:因为前缀、后缀均具有传递性,由Border的定义,易证Border具有传递性。

性质1.1.4:$T in Border(S) longleftrightarrow T_{1 ldots |T|-1} in Border(S_{1 ldots |S|-1}) land T_{|T|} = S_{|S|}$。

1.2、周期

我们称整数$t$是$S$的周期,当且仅当:$t in [1, |S|-1] land forall i in [1, |S|-t], S_i = S_{i+t}$,记作$t mid S$。

性质1.2.1:对于$S$的非空前缀$T$,$T$是$S$的Border当且仅当$|S|-|T|$是$S$的周期,这意味着非空Border与周期一一对应。

证明:$|S|-|T| mid S longleftrightarrow forall i in [1, |T|], S_i = S_{i+|S|-|T|} longleftrightarrow S_{1 ldots |T|} = S_{|S|-|T|+1 ldots |S|} longleftrightarrow T in Border(S)$。

性质1.2.2:对于$S$的真子串$T$,$|T|$是$S$的周期当且仅当$S$是一个由无限个$T$组成的串的子串(即$|T| mid S longleftrightarrow S subseteq TTldots$)。

证明留作习题。

1.3、MaxBorder

我们将非空字符串$S$最长的Border称作它的MaxBorder,记作$MaxBorder(S)$。

性质1.3.1:$S$所有异于其MaxBorder的Border都是其MaxBorder的Border。

(即$T in Border(S) land T eq MaxBorder(S) ightarrow T in Border(MaxBorder(S))$)。

证明:令$S$的MaxBorder为$B$。对于$S$中一个异于$B$的Border$T$,根据Border的定义可知$T$和$B$都是$S$的前缀,又因为$T$比$B$短,所以$T$是$B$的前缀。同理可证$T$也是$B$的后缀。于是易知$T$是$B$的Border。

1.4、失配树(Border Tree)

对于$S$的每一个前缀,将其与其MaxBorder连一条边,就会形成一颗以$varepsilon$为根的树,称作失配树(或者Border Tree)。

举个栗子,对于字符串 abacabaa ,它的Border Tree如下图所示:

(节点标号为其代表的前缀长度)

性质1.4.1:对于$S$的两个前缀$P$和$Q$,$Q$是$P$的Border当且仅当$Q$在失配树上是$P$的祖先。

证明:由Border的传递性可证明其充分性,性质1.3.1可证明其必要性。

2、next数组与KMP算法

2.1、next

对于一个长度为$N$的字符串$S$,记$next_i = |MaxBorder(S_{1 ldots i})|$。

根据性质1.1.4,我们可以由前$i-1$位的next找到满足$S_{1 ldots j} in Border(S_{1 ldots i-1}) land S_{j+1}=S_i$的$j$,从而递归求出$next_i=j$(当然,这样的$j$也有可能不存在,此时$next_i=0$)。

参考代码如下:

nxt[1]=0;
for(int i=2,j=0;i<=n;i++){
    while(j&&s[i]!=s[j+1])
        j=nxt[j];
    if(s[i]==s[j+1])
        j++;
    nxt[i]=j;
} 

性质2.1.1:$next_i leq next_{i-1}+1$。

2.2、字符串匹配问题以及BF算法

问题描述

给定一个长度为$N$的文本串$S$和一个长度为$M$模式串$T$,求出$T$在$S$中出现的全部位置。

BF(Brute Force)算法

BF算法的思想很暴力(毕竟名字就叫暴力算法),就是将$S$从每一位开始的长度为$m$的子串一一与$T$进行匹配,效率为$O(NM)$。

参考代码如下:

for(int i=1;i<=n;i++){
    bool flg=1;
    for(int j=1;j<=m;j++)
        if(s[i+j-1]!=t[j]){
            flg=0;
            break;
        }
    if(flg)
        printf("%d
",i);
}

2.3、KMP(Knuth-Morria-Pratt)算法

从名字就知道,这是由D.E.Knuth、J,H,Morris 和 V.R.Pratt三人共同发明的一个字符串匹配算法。

注意到BF算法中某一位匹配失败后都要从模式串的开头重新匹配,导致效率很低。而KMP算法就是针对这点利用next数组进行优化:根据Border的性质,在某一位失配后,可以直接从模式串的next处接着匹配。这样减少了文本串指针的回跳,从而使复杂度降低到了线性。

参考代码(可以看出与上文求next的代码很像):

for(int i=1,j=0;i<=n;i++){
    while(j&&s[i]!=t[j+1])
        j=nxt[j];
    if(s[i]==t[j+1])
        j++;
    if(j==m){
        printf("%d
",j-m+1);
        j=nxt[j];
    }
}

2.4、复杂度

空间复杂度

求next:$O(N)$;KMP算法:$O(N+M)$。

时间复杂度

求next:$O(N)$;KMP算法:$O(N+M)$。

证明:根据性质2.1.1易知,$i$每往后跳一位,$j$至多只能多跳一次next,于是$j$的变化次数与$i$同级。

inf、例题

[NOI2014] 动物园 (Border);

[UVa10298] Power String (周期);

[POI2006] OKR-Periods of Words (周期);

[洛谷P5829] 【模板】失配树 (失配树+LCA);

[POI2005] SZA-Template (失配树);

[洛谷P3375] 【模板】KMP字符串匹配 (KMP);

[HNOI2008] GT考试 (KMP+矩阵快速幂);

原文地址:https://www.cnblogs.com/Y25t/p/12459152.html