字符串
1.hash(yyds)
先鸽着
2.KMP
模式串匹配,利用一个前缀函数border,求出模式串失配时下次匹配的开始处,大大减小了时间开销。
设字符串T=“aabaaf”,我们求一下T的前缀表(用一个数组名为next的数组表示)。
第一个子串是t0=“a”,易知该子串没有前缀也没有后缀,故next[0]=0
第二个子串是t1=“aa”,该子串的前缀为"a",后缀也为"a",故next[1]=1
第三个子串是t2=“aab”,该子串的后缀中一定会有"b",前缀中一定不含有"b",则其没有相等的前后缀,故next[2]=0
第四个子串是t3=“aaba”,该子串的最大相等前后缀为"a",长度为1,故next[3]=1
第五个子串是t4=“aabaa”,该子串的最大相等前后缀为"aa",长度为2,故next[4]=2
第六个子串是t5=“aabaaf”,该子串的后缀中一定会有"f",前缀中一定不含有"f",则其没有相等的前后缀,故next[5]=0
例在aabaabaaf与aabaaf匹配时,第二个b与f不匹配,此时next[4]=2,因为border的前后缀相等,于是匹配可以直接跳到了它的后缀,这就实现了模式串的再次匹配。
那求KMP时只需维护一个前缀函数next[]
点击查看代码
#include<bits/stdc++.h>
using namespace std;
void get_Next(string s, int next[]) //这个函数对字符串s进行预处理得到next数组
{
int j = 0;
next[0] = 0; //初始化
for(int i = 1; i<s.size(); i++){ //i指针指向的是后缀末尾,j指针指向的是前缀末尾
while(j>0&&s[i]!=s[j]) j = next[j-1]; //前后缀不相同,去找j前一位的最长相等前后缀
if(s[i]==s[j]) j++; //前后缀相同,j指针后移
next[i] = j; //更新next数组
}
}
int strSTR(string s, string t) //这个函数是从s中找到t,如果存在返回t出现的位置,如果不存在返回-1
{
if(t.size()==0) return 0;
get_Next(t, next);
int j = 0;
for(int i = 0; i < s.size(); i++){
while(j>0&&s[i]!= t[j]) j = next[j-1];
if(s[i]==t[j]) j++;
if(j==t.size()) return i - t.size() + 1;
}
return -1;
}
引用原文链接:https://blog.csdn.net/qq_43869106/article/details/128753527
3.trie树
先鸽着
4.AC自动机
关于多个模式串的匹配,基于trie树的结构和KMP的思想实现,同样需要一个数组(可类比next[]存border长度),来记录模式串失配后应跳转的位置,我们把这个数组叫做fail
与 KMP 的 next 失配指针关系
相同点:两者同样是在失配的时候用于跳转的指针。
不同点:next 求的是最长的相同前后缀;而 fail 指针指向所有模式串的前缀中与当前节点“匹配”的“最长后缀”。
引用一下对fail的解释:
先规定从节点x到根节点路径上形成的字符串为sub(x)这个字符串中,第一个是根节点,最后一个是x节点上的字母。
如果sub(y)正好是sub(x)的后缀,并且不存在任意一个sub(y′)的长度大于 sub(y)的长度。那么fail(x)=y。

那么我们又要怎么去维护fail数组,这里我们就可以通过bfs来进行维护
基本模板
点击查看代码
#include"bits/stdc++.h"
using namespace std;
const int N=1e6+20;
int n,ans;
char p[N];
struct AC_automat
{
int tr[N][26];
int fail[N];
int mark[N];
int cnt;
queue<int>q;
void clear()
{
cnt=0;
memset(tr,0,sizeof tr);
memset(fail,0,sizeof fail);
memset(mark,0,sizeof mark);
while(!q.empty()) q.pop();
}
void insert(char *s)
{
int len=strlen(s);
int now=0;
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(tr[now][v]==0) tr[now][v]=++cnt;
now=tr[now][v];
}
mark[now]++;
}
void build()
{
for(int i=0;i<26;i++)
{
if(tr[0][i]!=0)
{
fail[tr[0][i]]=0;
q.push(tr[0][i]);
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(tr[u][i]!=0)
{
fail[tr[u][i]]=tr[fail[u]][i];
q.push(tr[u][i]);
}
else
{
tr[u][i]=tr[fail[u]][i];
}
}
}
}
int solve(char *s)
{
int len=strlen(s);
int now=0,ans=0;
for(int i=0;i<len;i++)
{
now=tr[now][s[i]-'a'];
for(int j=now;j&&~mark[j];j=fail[j])
{
ans+=mark[j];
mark[j]=-1;
}
}
return ans;
}
}AC;
int main()
{
while(scanf("%d",&n)&&n)
{
AC.clear();
for(int i=1;i<=n;i++)
{
scanf(" %s",p);
AC.insert(p);
}
AC.build();
scanf(" %s",p);
int ans=AC.solve(p);
printf("%d",ans);
}
return 0;
}
当然建图还是可以继续优化,很显然AC自动机不可能出现环,可以考虑通过拓扑排序优化
点击查看代码
void getfail() // 实际上也可以叫 build
{
for (int i = 0; i < 26; i++) trie[0].son[i] = 1;
q.push(1);
trie[1].fail = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
int Fail = trie[u].fail;
for (int i = 0; i < 26; i++) {
int v = trie[u].son[i];
if (!v) {
trie[u].son[i] = trie[Fail].son[i];
continue;
}
trie[v].fail = trie[Fail].son[i];
indeg[trie[Fail].son[i]]++; // 修改点在这里,增加了入度记录
q.push(v);
}
}
}
引用原文链接:https://blog.csdn.net/yikeyoucaihua/article/details/135637074

浙公网安备 33010602011771号