AC自动机的C++代码实现与过程讲解

AC自动机(Aho-Corasick algorithm)是一种多模式字符串匹配算法。它可以快速地查找多个模式串在一段文本串中出现的位置,并支持模式串的预处理,使得在查询时能够快速地匹配。

C++代码实现:

#include <iostream>
#include <queue>
#include <cstring>

using namespace std;

const int MAXN = 100005; //最大节点数
const int MAXM = 26; //最大字符集大小

struct Trie_Node //Trie树节点
{
    int id; //节点编号
    int nxt[MAXM]; //子节点
    int fail; //失败指针
    int val; //节点的输出值
}trie[MAXN];

int idx; //Trie树节点计数器

void trie_clear() //清空Trie树
{
    memset(trie, 0, sizeof(trie));
    idx = 0;
}

void trie_insert(char *s, int val) //往Trie树中插入一个字符串
{
    int len = strlen(s);
    int u = 0;
    for(int i = 0; i < len; i++)
    {
        int c = s[i] - 'a';
        if(!trie[u].nxt[c]) //新建节点
            trie[u].nxt[c] = ++idx;
        u = trie[u].nxt[c];
    }
    trie[u].val = val; //该节点的输出值为该字符串的权值
}

void ac_build() //构建AC自动机
{
    queue<int> q;
    for(int c = 0; c < MAXM; c++)
    {
        if(trie[0].nxt[c]) //所有的根节点的子节点的失败指针指向根节点
        {
            trie[trie[0].nxt[c]].fail = 0;
            q.push(trie[0].nxt[c]);
        }
    }
    while(!q.empty()) //BFS求出AC自动机的失败指针
    {
        int u = q.front();
        q.pop();
        for(int c = 0; c < MAXM; c++)
        {
            int v = trie[u].nxt[c];
            if(v)
            {
                trie[v].fail = trie[trie[u].fail].nxt[c]; //与父节点的失败指针相同分两种情况
                if(trie[trie[v].fail].val) //如果该节点的失败指针是可接受节点,那么当前节点就是可接受节点的后缀
                    trie[v].val = trie[trie[v].fail].val; //更新该节点的输出值
                q.push(v);
            }
            else
            {
                trie[u].nxt[c] = trie[trie[u].fail].nxt[c];
            }
        }
    }
}

char s[MAXN]; //输入的文本串
int pos[MAXN]; //所有模式串在文本串中的位置
bool vis[MAXN]; //标记每个出现过的节点
int ans; //输出值的和

void ac_query() //在Trie树上匹配文本串
{
    int u = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++) //从根节点开始匹配
    {
        int c = s[i] - 'a';
        u = trie[u].nxt[c]; //转移到下一个节点
        int v = u;
        while(v) //利用失败指针更新所有可接受的节点
        {
            if(trie[v].val && !vis[v])
            {
                vis[v] = true;
                ans += trie[v].val;
            }
            v = trie[v].fail;
        }
    }
}

int main()
{
    int n;
    while(cin >> n)
    {
        trie_clear();
        for(int i = 1; i <= n; i++)
        {
            int val;
            cin >> s >> val;
            trie_insert(s, val);
        }
        ac_build();
        cin >> s;
        memset(vis, false, sizeof(vis));
        ans = 0;
        ac_query();
        cout << ans << endl;
    }
    return 0;
}

过程讲解:

  1. 定义Trie树节点
struct Trie_Node //Trie树节点
{
    int id; //节点编号
    int nxt[MAXM]; //子节点
    int fail; //失败指针
    int val; //节点的输出值
};
  1. 往Trie树中插入一个字符串
void trie_insert(char *s, int val) //往Trie树中插入一个字符串
{
    int len = strlen(s);
    int u = 0;
    for(int i = 0; i < len; i++)
    {
        int c = s[i] - 'a';
        if(!trie[u].nxt[c]) //新建节点
            trie[u].nxt[c] = ++idx;
        u = trie[u].nxt[c];
    }
    trie[u].val = val; //该节点的输出值为该字符串的权值
}
  1. 构建AC自动机
void ac_build() //构建AC自动机
{
    queue<int> q;
    for(int c = 0; c < MAXM; c++)
    {
        if(trie[0].nxt[c]) //所有的根节点的子节点的失败指针指向根节点
        {
            trie[trie[0].nxt[c]].fail = 0;
            q.push(trie[0].nxt[c]);
        }
    }
    while(!q.empty()) //BFS求出AC自动机的失败指针
    {
        int u = q.front();
        q.pop();
        for(int c = 0; c < MAXM; c++)
        {
            int v = trie[u].nxt[c];
            if(v)
            {
                trie[v].fail = trie[trie[u].fail].nxt[c]; //与父节点的失败指针相同分两种情况
                if(trie[trie[v].fail].val) //如果该节点的失败指针是可接受节点,那么当前节点就是可接受节点的后缀
                    trie[v].val = trie[trie[v].fail].val; //更新该节点的输出值
                q.push(v);
            }
            else
            {
                trie[u].nxt[c] = trie[trie[u].fail].nxt[c];
            }
        }
    }
}
  1. 在Trie树上匹配文本串
void ac_query() //在Trie树上匹配文本串
{
    int u = 0;
    int len = strlen(s);
    for(int i = 0; i < len; i++) //从根节点开始匹配
    {
        int c = s[i] - 'a';
        u = trie[u].nxt[c]; //转移到下一个节点
        int v = u;
        while(v) //利用失败指针更新所有可接受的节点
        {
            if(trie[v].val && !vis[v])
            {
                vis[v] = true;
                ans += trie[v].val;
            }
            v = trie[v].fail;
        }
    }
}

最后,只需调用以上函数即可。

posted @ 2023-04-21 09:47  OIER笔记  阅读(203)  评论(0编辑  收藏  举报
返回顶部↑
友情链接: SSOJ OIER博客 ABC题解 ChatGPT体验 WIKIOI