字符串

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

posted @ 2024-07-11 20:56  zhengchenxi  阅读(46)  评论(1)    收藏  举报