哈希,kmp,trie树和AC自动机

1.哈希

字符串哈希实际上就是把一个字符串转化为一个数字

比如 abc=122+221+320 (也就是把 a 映射为1,b 映射为2,c 映射为3)

然后就没啥了。。。

关于自然溢出:

但在有时候,会出现两个字符串不相同但是整数相同的情况,此时我们把“进制”取为131/1331/13331...,模数一般取为 2641 ,因为 unsigned long long 可以自然溢出,所以就不用取模了。

核心代码:

1.准备工作

p[0]=1;
for(int i=1;i<maxn;i++)
{
p[i]=p[i-1]*P;
}
for(int i=1;i<=n;i++)
{
h[i]=h[i-1]*P+(ull)s[i]-'a';
}

2.求l,r区间的哈希值

ull gethash(int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}

注意,以上字符串是从1开始的。

2.KMP

显然,我还没有学懂

upd on:2024/7/2

这个算法主要解决的是字符串中关键字搜索。

暴力:从左到右一个一个匹配,时间复杂度为 O(nm) ,不够优秀。

KMP算法:利用已经部分匹配的有效信息,只修改模式串的指针,让模式串尽量移动到有效位置。

由于我没有图并且自己也不想画,下面的请自行脑补,见谅。

会发现在主串(i 指针)和模式串(j 指针)不匹配的情况下 j 指针要移动到 最长相同前后缀前缀 的下一个位置(假设这一位为 k )。因为前后缀都相同了,所以就不用匹配了,直接从下一个位置开始,利用了已经匹配的有效信息。

数学公式表达:p[0 ~ k1]==p[jk ~ j1](p 为模式串)

定义next 数组表示当 i,j 指针失配时 j 要跳转的位置。

p[k]==p[j] 的时候,next[j+1]=next[j]+1

//以下是处理next数组
void getnext(string s,int nxt[])
{
int n=s.size();
int j=0;
nxt[0]=0;
for(int i=1;i<n;i++)
{
while(j>0&&s[i]!=s[j])
{
j=nxt[j-1];
}
if(s[i]==s[j]) j++;
nxt[i]=j;
}
}
//以下是匹配
void kmp(string s,string t)
{
int n=s.size(), m=t.size();
for(int i=0,j=0;i<n;i++)
{
while(j>0&&s[i]!=t[j]) j=nxt[j-1];
if(s[i]==t[j]) j++;
if(j==m)
{
j=nxt[j-1];
//i就是t串末尾
}
}
}

3.trie树

差不多就是把一些字符串转化为一棵树

是一种用于快速查询某个字符串/字符前缀是否存在的数据结构。

插入/生成

//idx代表当前字符的编号,根节点为0
//son数组一维下标是父节点的idx,二维下标是这个父节点的直接子节点的str[i]-'a'的值
//cnt数组表示以该idx结尾的字符串的个数,例如:有几个'abc'的字符串
void insert(char str[])
{
int p=0;
for(int i=0;str[i];i++)
{
int u=str[i]-'a';
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}

查询

int query(char str[])//查询字符串出现的次数
{
int p=0;
for(int i=0;str[i];i++)
{
int u=str[i]-'a';
if(!son[p][u]) return 0;
p=son[p][u];
}
return cnt[p];
}

例如:sea,she,sell 我们可以得到下面的Trie
点我

关于trie树,第2,3两道题,推荐这篇题解 》》》( ⊙ o ⊙ )啊!

4.AC自动机

由于前面学的知识都忘了,一整个复习+回顾代码,发现很难肝,顺便修整了博客(

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5, M=1e6+5;
int n, son[N][26], cnt[N], idx, T;
char s[100], s1[M];
int fail[N];//失配指针
void insert(char str[])
{
int p=0;
for(int i=0;str[i];i++)
{
int u=str[i]-'a';
if(!son[p][u]) son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}
void build()//建AC自动机
{
queue<int> q;
for(int i=0;i<26;i++)
{
if(son[0][i]) q.push(son[0][i]);
}
while(q.size())
{
int u=q.front();//用于BFS遍历字典树
q.pop();
for(int i=0;i<26;i++)
{
int v=son[u][i];
if(v) //存在
{
fail[v]=son[fail[u]][i];//失配指针就是父节点的失配指针的同样的儿子
q.push(v);
}
else son[u][i]=son[fail[u]][i];//一种特殊处理,很像并查集里的路径压缩
}
}
}
int query(char str[])
{
int ans=0, u=0;
for(int i=0;str[i];i++)
{
u=son[u][str[i]-'a'];
for(int j=u;j&&cnt[j]!=-1;j=fail[j])
{
ans+=cnt[j], cnt[j]=-1;//防止重复计算
}
}
return ans;
}
int main()
{
cin>>T;
while(T--)
{
memset(fail, 0, sizeof(fail));
memset(son, 0, sizeof(son));
memset(cnt, 0, sizeof(cnt));
cin>>n;
for(int i=0;i<n;i++)
{
cin>>s;
insert(s);
}
cin>>s1;
build();
cout<<query(s1)<<endl;
}
return 0;
}

即使所有人都放弃你,仍有几十亿细胞为你而活。

posted @   zhouyiran2011  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示