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;
}