KMP & AC自动机

KMP 和 AC自动机 都可以解决字符串匹配问题
KMP是一对一匹配
AC自动机是多对一匹配

KMP

KMP核心思想是 利用字符串的前缀与后缀相同,失配时跳到等于后缀的前缀,可以不必从头开始
这张图列举了字符"ABCDABD"所有的前缀和后缀 红色的表示前缀和后缀相等
在这里插入图片描述
例如:
用字符 P=“ABCDABCDE” 去匹配 字符 S = “ABCDABCDABCDABCD”
当匹配到 ABCDABCDABCDABCD时
发现’A’与’E’不匹配
那指针就可以不必从头开始,可以跳到ABCDABCDE 从A开始匹配
原理很简单,就是因为后缀等于前缀,那么本来后缀能匹配的上的前缀依然能匹配上
现在开始构造后缀等于前缀的指针数组 (next)
这里用到了DP的思想
当计算第i个字符的next指针
去找第i-1个字符的next指针所指向的字符
  要是这个字符的下一个字符等于第i个字符 那么第i个字符的next指针指向这个字符的下标
  否则继续去寻找这个字符反而next指针所指向的字符(开始循环)
原理还是很简单
因为要找第i个字符next指针,第i-1个字符已经找到到它的next
所以就在第i-1个字符所构成的后缀 与之相等的前缀的后面一个去找第i个字符
在这里插入图片描述

code
/*
https://www.luogu.org/problem/P3375
P3375
【模板】KMP字符串匹配
*/
#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<cstring>
using namespace std;
#define maxn 1000005
char s[maxn];
char p[maxn];
int ans[maxn];
int next_[maxn];
int cnt;
int lens,lenp;
void build(){
  next_[0]=-1;
  int k=-1;
  for(int i=1;i<lenp;i++){
    while(k!=-1&&p[k+1]!=p[i]) k=next_[k];
    if(p[k+1]==p[i]) k++;
    next_[i]=k;
  }
}
void kmp(){
  cnt=0;
  lenp=strlen(p);
  lens=strlen(s);
  build();
  int j=-1;
  for(int i=0;i<lens;i++){
    while(j!=-1&&p[j+1]!=s[i]) j=next_[j];
    if(p[j+1]==s[i]) j++;
    if(j==lenp-1){
      ans[cnt++]=i-lenp+2;
      j=next_[j];
    }
  }
}
int main(){
  scanf("%s%s", s, p);
  kmp();
  for(int i=0;i<cnt;i++){
    printf("%d\n", ans[i]);
  }
  for(int i=0;i<lenp;i++){
    printf("%d ", next_[i]+1);
  }
  return 0;
}

AC自动机

AC自动机 核心思想是 在由所有字符串p所构成的字典(前缀树)树上跑KMP
要跑KMP 就要构造next数组,在这里叫它失配指针fail
同样构造fail指针要用到dp思想
其实和KMP找next指针一样
依旧还是去找它的父亲节点的失配指针的字符的儿子有无这个字符
  要是有就是它的失配指针
  要是没有就继续找它的失配指针 就和KMP一样
在这里插入图片描述

code
/*
https://www.luogu.org/problem/P3796
P3796
【模板】AC自动机(加强版)
*/
#include <queue>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
using namespace std;
char a[160][100];
const int maxn =  1*1e6+9;
int trie[maxn][26]; //字典树
int fail[maxn];     //失败时的回溯指针
int cnt = 0;
int ans[160];
int mp[maxn];
int anss;
void insertWords(char s[],int id){
    int root = 0;
    int len=strlen(s);
    for(int i=0;i<len;i++){
        int next = s[i] - 'a';
        if(!trie[root][next])
            trie[root][next] = ++cnt;
        root = trie[root][next];
    }
    mp[root]=id;
}
void getFail(){
    queue <int>q;
    for(int i=0;i<26;i++){
        if(trie[0][i]){
            fail[trie[0][i]] = 0;
            q.push(trie[0][i]);
        }
    }
    while(!q.empty()){
        int now = q.front();
        q.pop();
        for(int i=0;i<26;i++){
            if(trie[now][i]){
                fail[trie[now][i]] = trie[fail[now]][i];
                q.push(trie[now][i]);
            }
            else{
                trie[now][i] = trie[fail[now]][i];
            }
        }
    }
}
void query(char s[]){
    int now = 0;
    int len=strlen(s);
    for(int i=0;i<len;i++){
        now = trie[now][s[i]-'a'];
        for(int j=now;j;j=fail[j]){
            ans[mp[j]] ++;
        }
    }
}
char s[maxn];
int main() {
    int n;
    while(~scanf("%d", &n)&& n){
      memset(ans,0,sizeof ans);
      memset(fail,0,sizeof fail);
      memset(trie,0,sizeof trie);
      memset(mp,0,sizeof mp);
      cnt=0;
      anss=0;
      for(int i=1;i<=n;i++){
          scanf("%s", a[i]);
          insertWords(a[i],i);
      }
      getFail();
      scanf("%s", s);
      query(s);
      for(int i=1;i<=n;i++){
        if(ans[i]>anss) anss=ans[i];
      }
      printf("%d\n", anss);
      for(int i=1;i<=n;i++){
        if(ans[i]==anss){
          printf("%s\n", a[i]);
        }
      }
    }
    return 0;
}
发布了70 篇原创文章 · 获赞 22 · 访问量 6499

猜你喜欢

转载自blog.csdn.net/weixin_44410512/article/details/101109082
今日推荐