Tria树(前缀树)与AC自动机

2023-11-10 19:40
文章标签 前缀 ac 自动机 tria

本文主要是介绍Tria树(前缀树)与AC自动机,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • Tria树(前缀树)
    • 介绍
    • 数据结构
    • 插入,搜索,查找
  • AC自动机
    • 介绍
  • 板子题
      • AC代码:
      • 使用指针构建结点但是无法AC的代码

Tria树(前缀树)

介绍

前缀树是一种用于插入查找搜索数据的数据结构,又叫做字典树。后缀树与其类似。和哈希表相比,前缀树不仅可以查找某一个键,也可以查找该键的前缀。并且查找速度只与所要查找的键的字符长度有关。

数据结构

一个只存储小写字母的tria树的数据结构如下:

struct Trienode{bool is_string = false; //是否是结尾Trienode* next[26] = {NULL};
}*root;

插入,搜索,查找

  • 初始化
root = new Trienode();	//初始化
  • 插入
void insert(string word) {Trienode *p = root;for(int i=0;i<word.size();i++){if(p->next[word[i]-'a']==NULL)p->next[word[i]-'a'] = new Trienode();p = p->next[word[i]-'a'];}p->is_string = true;return;
}
  • 查找字符串是否存在
bool search(string word) {Trienode *p = root;for(int i=0;i<word.size();i++){if(p->next[word[i]-'a']==NULL)return false;p = p->next[word[i]-'a'];}return p->is_string;
}
  • 查找前缀是否存在
bool startsWith(string prefix) {Trienode*p =root;for(int i=0;i<prefix.size();i++){if(p->next[prefix[i]-'a']==NULL)return false;p = p->next[prefix[i]-'a'];}return true;
}

AC自动机

介绍

AC自动机,结合了KMP和Tria树。给tria树中的每个结点增加了一个fail指针,当匹配失败时跳转。(找到当前已经有的前缀相等的最长后缀!) 。

  • 构造fail指针的思路如下:

通过bfs(队列)构造fail指针,根结点无fail指针(或者指向空).第一层fail指针指向根节点。当一个结点A遇到字母’a’失配时,他的父亲结点B通过’b’匹配带该结点,找到他父亲的fail指针指向的结点,如果他的父亲的fai指针指向的结点能够匹配’b’到结点C,则结点A的fail结点指向结点C。(B不符合则循环向fail找,一只找不到,则指向根结点)

以单模式串"ababac"为例:(蓝色为fail指针)(相当于kmp算法的AC自动机)
在这里插入图片描述

板子题

以一道题目给出写出模版:
P3808 【模板】AC自动机(简单版)
在这里插入图片描述

AC代码:

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;const int maxn = 1000006;	//结点个数 struct Aho{struct trienode{int next[26];int fail=-1;int cnt=0;	//是否匹配到模式串 }ac[maxn];queue<int> que;int ac_num=1;void init(){	//自动机初始化 for(int i=0;i<maxn;i++){memset(ac[i].next,0,sizeof(ac[i].next));ac[i].fail = ac[i].cnt = 0;}ac[0].fail = -1;ac_num = 1;}void insert(char *word) {int p = 0;int size = strlen(word);for(int i=0;i<size;i++){char c = word[i];if(ac[p].next[c-'a']==0)ac[p].next[c-'a'] = ac_num++;p = ac[p].next[c-'a'];}ac[p].cnt++;return;}void build(){ac[0].fail = -1;que.push(0);while(!que.empty()){int p=que.front();que.pop();for(int i=0;i<26;i++){if(ac[p].next[i]!=0){int p2 = ac[p].fail;while(p2!=-1){if(ac[p2].next[i]!=0){ac[ac[p].next[i]].fail = ac[p2].next[i];break;}p2 = ac[p2].fail;}if(p2==-1) ac[ac[p].next[i]].fail = 0; que.push(ac[p].next[i]);}}}return;}int query(char *words){int n = strlen(words);int res=0;int p = 0;for(int i=0;i<n;i++){char c = words[i];if(ac[p].next[c-'a']!=0)p = ac[p].next[c-'a'];else{p=ac[p].fail;while(p!=-1&&ac[p].next[c-'a']==0)p = ac[p].fail;if(p==-1) p = 0;else p = ac[p].next[c-'a'];}for(int p2=p;p2!=-1&&ac[p2].cnt!=-1;p2=ac[p2].fail){res+=ac[p2].cnt;ac[p2].cnt = -1;	//此处是一个优化点! }}return res;}
}aho;char p[1000006];
int main(){int n;scanf("%d",&n);for(int i=0;i<n;i++){	//输入模式串scanf("%s",p);aho.insert(p);}aho.build();scanf("%s",p);printf("%d",aho.query(p));return 0;
}

使用指针构建结点但是无法AC的代码

自己检查了很多遍找不到错误啊啊啊啊啊,把测试样例下载下来数据跑不起来,修改了N遍对照两个代码结果都一样啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊!但是第二个测试样例死活就是过不去啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;struct Aho{struct trienode{trienode* next[26]={NULL};trienode* fail=NULL;int cnt;	//是否匹配到模式串 }*root=new trienode();queue<trienode*> que;void insert(char *word) {trienode *p = root;int size = strlen(word);for(int i=0;i<size;i++){if(p->next[word[i]-'a']==NULL)p->next[word[i]-'a'] = new trienode();p = p->next[word[i]-'a'];}p->cnt=1;return;}void build(){while(!que.empty()) que.pop();que.push(root);while(!que.empty()){trienode* p=que.front();que.pop();for(int i=0;i<26;i++){if(p->next[i]!=NULL){trienode* p2 = p->fail;while(p2!=NULL){if(p2->next[i]!=NULL){p->next[i]->fail = p2->next[i];break;}p2 = p2->fail;}if(p2==NULL) p->next[i]->fail = root; que.push(p->next[i]);}}}return;}int query(char *words){int n = strlen(words);int res=0;trienode *p = root;for(int i=0;i<n;i++){char c = words[i];if(p->next[c-'a']!=NULL)p = p->next[c-'a'];else{p=p->fail;while(p!=NULL&&p->next[c-'a']==NULL) p = p->fail;if(p==NULL) p = root;else p = p->next[c-'a'];}for(trienode* p2=p;p2!=NULL&&p2->cnt!=-1;p2=p2->fail){res+=p2->cnt;p2->cnt = -1;	//此处是一个优化点 }}return res;}
}aho;char p[1000006];
int main(){int n;scanf("%d",&n);for(int i=0;i<n;i++){	//输入模式串scanf("%s",p);aho.insert(p);}aho.build();scanf("%s",p);printf("%d",aho.query(p));return 0;
}

这篇关于Tria树(前缀树)与AC自动机的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/384847

相关文章

hdu 3065 AC自动机 匹配串编号以及出现次数

题意: 仍旧是天朝语题。 Input 第一行,一个整数N(1<=N<=1000),表示病毒特征码的个数。 接下来N行,每行表示一个病毒特征码,特征码字符串长度在1—50之间,并且只包含“英文大写字符”。任意两个病毒特征码,不会完全相同。 在这之后一行,表示“万恶之源”网站源码,源码字符串长度在2000000之内。字符串中字符都是ASCII码可见字符(不包括回车)。

POJ 1625 自动机

给出包含n个可见字符的字符集,以下所提字符串均由该字符集中的字符构成。给出p个长度不超过10的字符串,求长为m且不包含上述p个字符串的字符串有多少个。 g++提交 int mat[108][108] ;int matn ;int N ;map<char ,int> to ;//ACconst int maxm = 108 ;const int kin

zoj 3228 ac自动机

给出一个字符串和若干个单词,问这些单词在字符串里面出现了多少次。单词前面为0表示这个单词可重叠出现,1为不可重叠出现。 Sample Input ab 2 0 ab 1 ab abababac 2 0 aba 1 aba abcdefghijklmnopqrstuvwxyz 3 0 abc 1 def 1 jmn Sample Output Case 1 1 1 Case 2

D4代码AC集

贪心问题解决的步骤: (局部贪心能导致全局贪心)    1.确定贪心策略    2.验证贪心策略是否正确 排队接水 #include<bits/stdc++.h>using namespace std;int main(){int w,n,a[32000];cin>>w>>n;for(int i=1;i<=n;i++){cin>>a[i];}sort(a+1,a+n+1);int i=1

【LeetCode热题100】前缀和

这篇博客共记录了8道前缀和算法相关的题目,分别是:【模版】前缀和、【模版】二维前缀和、寻找数组的中心下标、除自身以外数组的乘积、和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和。 #include <iostream>#include <vector>using namespace std;int main() {//1. 读取数据int n = 0, q = 0;ci

前缀和 — 利用前缀信息解决子数组问题

【前缀和的核心思想是预先处理数组来快速计算任意子数组的和,基本上用于数组和序列问题。】 前缀和算法具体步骤 构造前缀和数组: 给定一个数组nums,其前缀和数组prex定义为prex[i]表示为数组nums从起始位置到第i个位置的元素累加和。构建前缀和公式: p r e x [ i ] = n u m s [ i ] ( i = = 0 ) p r e x [ i ] = p r e x

正规式与有限自动机例题

答案:D 知识点: 正规式 正规集 举例 ab 字符串ab构成的集合 {ab} a|b 字符串a,b构成的集合 {a,b} a^* 由0或者多个a构成的字符串集合 {空,a,aa,aaa,aaaa····} (a|b)^* 所有字符a和b构成的串的集合 {空,a,b,ab,aab,aba,aaab····} a(a|b)^* 以a为首字符的a,b字符串的集

HDU 3037 今年暑假不AC

题目: http://acm.hdu.edu.cn/showproblem.php?pid=2037 题解: 对结束时间排序,然后进行一次遍历,寻找开始时间不小于上一个结束时间的节目。 代码: #include<stdio.h>#include<iostream>using namespace std;struct program{int start,end;}p[101

每日一题~cf 970 div3 (A思维,B小模拟,C二分,D排列数建图成环,E 26个字母暴力+前缀和,F 逆元,G 数论gcd )

A 题意: 有 a 个1 ,b 个2.问是否能将这些数划分为两个数值相等的集合。 输出 YES 或者 NO —————— 问题等价于 将数组 分成两个数值相同的数组。所以sum 应该是偶数。也就是说 1 的个数是偶数。在i1的个数是偶数的情况下,将 2 分成两份,如果2 的个数是偶数,OK。如果是奇数那么需要1来补齐,如果1 的个数大于等于2那么可以补齐。(1 的个数是偶数,需要2个1来补齐,剩下

基于 AC 驱动的电容结构 GaN LED 模型开发和应用

随着芯片尺寸减小,微小尺寸GaN 基 Micro LED 显示面临着显示与驱动高密度集成的难题,传统直流(DC)驱动技术会导致结温上升,降低器件寿命。南京大学团队创新提出交流(AC)驱动的单电极 LED(SC-LED)结构【见图1】,利用隧穿结(TJ)降低器件的交流工作电压。为了深入理解该器件的工作原理,我司技术团队开发了基于 AC 驱动的物理解析模型,揭示了隧穿结降低器件工作电压的