KMP算法 学习笔记

概述

一种字符串匹配算法,是前缀函数的一个应用。

下文的字符串为 1-index。

前缀函数

对于字符串 \(s\),定义 \(\bold{Border}(s)\) 为对于 \(i \in \left[1, |s|\right)\),满足 \(pre_i=suf_i\) 的字符串 \(pre_i\) 的集合。\(\bold{Border}(s)\) 中的每个元素都称之为字符串 \(s\)\(\operatorname{border}\)。也就是字符串的最长子串,满足它既是真前缀也是真后缀。

对于一个字符串,定义其前缀函数是一个数组 \(\pi\)\(\pi[i]\) 是前缀 \(pre_i\) 的最长 \(\operatorname{border}\) 的长度。

假如已知 \(\pi[i]\)\(\pi[i+1]\)。如果 \(s_{\pi[i]+1}=s_{i+1}\),那么这两个字符可以与上一个 \(\operatorname{border}\) 拼接,则 \(\pi[i+1]=\pi[i]+1\)。否则设 \(pre_i\) 的第 \(n\) 长的 \(\operatorname{border}\)\(j^{(n)}\)。如图,根据 \(\operatorname{border}\) 的定义,四段长 \(j\) 的子串相等,那么 \(j^{(2)}=\pi[\pi[i]]\)。同理 \(j^{(n)}=\pi[j^{(n-1)}]\)

一直迭代 \(j\) 直到 \(s_{j+1}=s_{i+1}\)\(j=0\),此时 \(j\) 就是 \(\pi[i+1]\)

void getnxt(int m,char t[],int nxt[]){
  nxt[1]=0;
  for(int i=2,j=0;i<=m;i++){
    while(j&&t[j+1]!=t[i])j=nxt[j];
    if(t[j+1]==t[i])j++;
    nxt[i]=j;
  }
}

字符串匹配

KMP算法在失配时,不直接清空匹配位数,而是利用前缀函数保留一些匹配位数。

如图,假设匹配到主串的第 \(i\) 位,模式串的第 \(j\) 位。失配时,将模式串后移一个位置。四个小的方框表示 \(pre_j\)\(\operatorname{border}\)。将 \(t\) 的真前缀移动到真后缀上,四个小的方框相等。

枚举 \(i\),因为要往后增加 \(i\),所以手动尝试匹配一位。如果失配,就不断缩小后移的位数。

int kmp(int n,char s[],int m,char t[],int nxt[],int ans[]){
  int cnt=0;
  for(int i=1,j=0;i<=n;i++){
    while(j&&t[j+1]!=s[i])j=nxt[j];
    if(t[j+1]==s[i])j++;
    if(j==m)ans[++cnt]=i-m+1;
  }
  return cnt;
}

字符串匹配的代码与求前缀函数很像,求前缀函数可以看作模式串自己跟自己匹配。

复杂度证明:每次 \(j\) 可以一直减少,但是最多增加一,因此是 \(O(n+m)\)

失配树

P5829

对于每个 \(i\) 连边 \(nxt_i\) 就形成了失配树。在失配树上,一个节点的所有祖先表示了一个前缀的所有 \(\operatorname{border}\)

对于这道题,建出失配树。则 \(pre_u,pre_v\) 的最长公共 \(\operatorname{border}\) 就是 \(u,v\) 最近公共祖先。特殊情况是一个节点是另一个节点的祖先,由于 \(\operatorname{border}\) 不包含串本身,答案为最近公共祖先的父亲。

#include<bits/stdc++.h>
using namespace std;
char s[1000005];
int n,m,nxt[1000005],lg[1000005],dep[1000005],f[1000005][21];
vector<int>e[1000005];
void getnxt(int m,char t[],int nxt[]){
  nxt[1]=0;
  for(int i=2,j=0;i<=m;i++){
    while(j&&t[j+1]!=t[i])j=nxt[j];
    if(t[j+1]==t[i])j++;
    nxt[i]=j;
  }
}
int lca(int u,int v){
  if(dep[u]<dep[v])swap(u,v);
  while(dep[u]>dep[v])u=f[u][lg[dep[u]-dep[v]]];
  if(u==v)return f[u][0];
  for(int i=lg[dep[u]];i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];
  return f[u][0];
}
int main(){
  cin>>s+1,n=strlen(s+1),getnxt(n,s,nxt);
  for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
  for(int i=1;i<=n;i++){
    f[i][0]=nxt[i],dep[i]=dep[nxt[i]]+1;
    for(int j=1;j<=lg[dep[i]]+1;j++)f[i][j]=f[f[i][j-1]][j-1];
  }
  cin>>m;
  for(int i=1,u,v;i<=m;i++)cin>>u>>v,cout<<lca(u,v)<<'\n';
  return 0;
}

[[字符串]]

posted @ 2024-03-01 09:38  lgh_2009  阅读(1)  评论(0编辑  收藏  举报