AC自动机模版——字符串

概述

  • 作用
  1. AC自动机主要用于解决多模式串的匹配问题(具体指的是:如下图中 模式串 p中有多个 字符串,问这些字符串在 主串s的出现的总次数),
  2. 是字典树(trie树)的变种,一种伪树形结构(主体是树形的,但是由于加入了失败指针,使得它变成了一个有向图);
  • 过程
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

讲解

讲解视频

代码

#include<iostream>
#include<string.h>
#include<string>
#include<vector>
#include<queue>
using namespace std;

struct Trie
{
    int son[26] = { 0 };
    int cnt = 0;                //以当前节点为终点的 模式字符串 的数量
    int fail = -1;              //fail == -1 表示没有指向的节点了,fail == 0 表示当前节点的 fail指针指向 根节点0,,,注意根节点0 也是没有指向的节点的,所以它的fail指针也是指向 -1点
};
vector<Trie> trie;
int idx = 0;

void insert(string s)
{
    int p = 0;
    for(int i = 0; i < s.size(); i ++)
    {
        int c = s[i] - 'a';
        if(trie[p].son[c] == 0) trie[p].son[c] = ++ idx;
        p = trie[p].son[c];
    }
    trie[p].cnt ++;
}

void fail_pre()
{
    queue<int> q;
    for(int i = 0; i < 26; i ++)
    {
        if(trie[0].son[i])
        {
            int sn = trie[0].son[i];
            trie[sn].fail = 0;
            q.push(sn);
        }
    }

    while(q.size())
    {
        int f = q.front(); q.pop();
        for(int i = 0; i < 26; i ++)
        {
            if(trie[f].son[i])
            {
                int now = trie[f].son[i];
                int ffail = trie[f].fail;
                while(~ ffail && ! trie[ffail].son[i]) ffail = trie[ffail].fail;
                if(~ ffail) trie[now].fail = trie[ffail].son[i];
                else trie[now].fail = 0;
                q.push(now);
            }
        }
    }
}

int query(string s)
{
    int ans = 0;
    int p = 0;
    for(int i = 0; i < s.size(); i ++)
    {
        int c = s[i] - 'a';
        while(! trie[p].son[c] && ~ trie[p].fail) p = trie[p].fail;     //注意:“ ! trie[p].son[c] ” 表示的意思是p这个节点为叶子节点
        if(trie[p].son[c]) p = trie[p].son[c];          //如果找到了 同字符的儿子,用p记录下这个儿子的编号,
        else continue;                                  //如果没有找到,p 的值被赋值为 根节点的编号0

        int p2 = p;
        while(~ trie[p2].fail)      //注意 抛去不存在节点的fail指针值为-1,那么只有 根节点的0点fail指针为-1, 那么这个while循环里面的意思是:当p不为根节点的时候
        {
            ans += trie[p2].cnt;
            p2 = trie[p2].fail;
        }
    }
    return ans;
}


int main()
{
    trie.resize(1e5);           //树中最大的节点的数量1e5   
    insert("she");
    insert("he");
    insert("her");
    insert("his");
    insert("this");
    insert("is");
    fail_pre();
    int ans = query("sherthis");
    printf("%d\n", ans);

    return 0;
}

另一个模版

#include <queue>
#include <cstdlib>
#include <cmath>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn =  2*1e6+9;

int trie[maxn][26]; //字典树
int cntword[maxn];  //记录该单词出现次数
int fail[maxn];     //失败时的回溯指针
int cnt = 0;

void insertWords(string s){
    int root = 0;
    for(int i=0;i<s.size();i++){
        int next = s[i] - 'a';
        if(!trie[root][next])
            trie[root][next] = ++cnt;
        root = trie[root][next];
    }
    cntword[root]++;      //当前节点单词数+1
}
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]);
        }
    }

    //fail[now]    ->当前节点now的失败指针指向的地方
    //tire[now][i] -> 下一个字母为i+'a'的节点的下标为tire[now][i]
        while(!q.empty()){
            int now = q.front();
            q.pop();

            for(int i=0;i<26;i++)
            {      //查询26个字母
                if(trie[now][i]){
                    //如果有这个子节点为字母i+'a',则
                    //让这个节点的失败指针指向(((他父亲节点)的失败指针所指向的那个节点)的相同字符节点)
                    //有点绕,为了方便理解特意加了括号

                    fail[trie[now][i]] = trie[fail[now]][i];
                    q.push(trie[now][i]);
                }
                else//否则就让当前节点的这个子节点
                    //指向当前节点fail指针的这个子节点
                    trie[now][i] = trie[fail[now]][i];
            }
        }
}


int query(string s){
    int now = 0,ans = 0;
    for(int i=0;i<s.size();i++){    //遍历文本串
        now = trie[now][s[i]-'a'];  //从s[i]点开始寻找
        for(int j=now;j && cntword[j]!=-1;j=fail[j]){
            //一直向下寻找,直到匹配失败(失败指针指向根或者当前节点已找过).
            ans += cntword[j];
            cntword[j] = -1;    //将遍历国后的节点标记,防止重复计算
        }
    }
    return ans;
}

int main() {
    int n;
    string s;
    cin >> n;
    for(int i=0;i<n;i++){
        cin >> s ;
        insertWords(s);
    }
    fail[0] = 0;
    getFail();
    cin >> s ;
    cout << query(s) << endl;
    return 0;
}

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我可以为您讲解一下如何用自动机(DFA)识别输入字符串自动机是一个抽象的数学模型,用于描述一些计算模型,例如字符串识别、语言分析等。在字符串识别中,自动机可以用来判断输入的字符串是否符合特定的规则。 一般来说,自动机有两种类型:确定性自动机(DFA)和非确定性自动机(NFA)。DFA 是一种简单的自动机类型,它具有以下特点: 1. DFA 有一个有限的状态集合,每个状态都代表一个特定的状态。 2. DFA 有一个输入字母表,也就是可以接受的输入字符的集合。 3. DFA 有一个状态转移函数,它描述了 DFA 在各个状态下接受不同输入字符的转移情况。 4. DFA 有一个起始状态和一些终止状态,起始状态是 DFA 的初始状态,终止状态是 DFA 接受字符串的状态。 在使用 DFA 进行字符串识别时,我们需要先构建一个 DFA,然后将输入的字符串逐个字符输入到 DFA 中,根据状态转移函数的定义,DFA 会从当前状态转移到下一个状态,直到输入字符串结束。如果最后停留在一个终止状态,那么说明输入的字符串符合规则,否则不符合规则。 具体实现时,我们可以使用编程语言来实现 DFA,也可以使用工具生成 DFA 的代码。在实现时,我们需要注意状态转移函数的定义和终止状态的设置,以确保 DFA 能够正确地识别输入的字符串。 希望这个简单的介绍能够帮助您理解如何使用自动机识别输入字符串。 ### 回答2: 自动机是一种抽象的计算模型,可以用于识别和处理输入的字符串自动机包括有限状态自动机(DFA)和非确定性有限状态自动机(NFA)两种形式。 在识别输入字符串的过程中,自动机通过状态转移函数和输入字符逐步遍历输入字符串,直到遍历完字符串或达到终止状态。自动机的状态表示着从初始状态到当前状态的路径信息,而状态转移函数则指导了自动机根据输入字符从一个状态转移到另一个状态。 自动机的基本逻辑如下: 1. 确定初始状态,通常为自动机的第一个状态。 2. 从初始状态开始,根据输入字符和状态转移函数的指导,将自动机从一个状态转移到另一个状态。 3. 若输入字符串未遍历完且无法进行状态转移,则表示输入字符串无法被自动机识别。 4. 若输入字符串遍历完且自动机处于终止状态,则表示输入字符串可以被自动机识别。 自动机的设计与实现需要考虑以下几个关键要素: 1. 确定状态集合和状态转移函数,以及初始状态和终止状态的定义。 2. 考虑输入字符的类型和范围,确定输入字母表或输入符号集合。 3. 针对输入字符串的具体需求,设计和编码自动机的状态转移函数。 4. 通过测试和验证,验证自动机的正确性和有效性。 需要注意的是,自动机作为一种计算模型,在实际应用中具有广泛的应用,如正则表达式匹配、编译原理中的词法分析等。因此,了解和掌握自动机的基本原理和设计方法对于理解计算机科学和相关领域的问题具有重要意义。 ### 回答3: 自动机是一种用于识别或处理输入字符串的计算机模型。它包括状态、输入字母表、转移函数和初始状态集合。 状态:自动机的状态表示了目前所处的情况。根据识别字符串的需求不同,状态可以有多个。例如,对于一个简单的自动机,可以有两个状态:接受状态和拒绝状态。 输入字母表:输入字母表是自动机所能接受的字符集合。可以是任意的字符,例如英文字母、数字等。 转移函数:转移函数描述了自动机如何根据当前状态和输入字符进行转移。转移函数的定义可以是一个状态和输入字符的映射关系。例如,当自动机处于状态A,输入字符为b时,通过转移函数可以转移到状态B。 初始状态集合:初始状态集合包含了自动机在开始处理输入字符串之前所处的状态。可以有一个或多个初始状态。 使用自动机识别输入字符串的过程如下: 1. 确定需要识别的字符串规则,例如判断输入字符串是否是一个合法的手机号码。 2. 根据规则设计自动机的状态、输入字母表、转移函数和初始状态集合。 3. 读入输入字符串,并根据转移函数和当前状态进行状态转移。 4. 如果在识别过程中出现无法转移的情况,或者最终状态不是接受状态,则判定输入字符串不符合规则。 5. 如果最终状态是接受状态,则判定输入字符串符合规则。 通过使用自动机可以有效地识别或处理各种类型的输入字符串,例如词法分析、语法分析、正则表达式匹配等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值