AC自动机(Aho-Corasick 多模式匹配算法)

AC自动机是解决多模式串匹配算法,常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。

AC自动机一般用三步:

  1.建立模式的Trie(字典树)

          字典树模板:https://blog.csdn.net/baodream/article/details/80685799

  2.给Trie添加失败路径(fail指针)

         推荐这篇博客:https://blog.csdn.net/creatorx/article/details/71100840

        概括起来就一句话:设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母     为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。

  使用广度优先搜索BFS,层次遍历节点来处理,每一个节点的失败路径。

  3.根据AC自动机,搜索待处理的文本

 

数组模拟代码:

const int MAXN = 5e5+5;
#define son_num 26    //注意修改
struct Trie{
    int tree[MAXN][son_num];  //26是讨论全小写字母情况,根据题意修改
    int fail[MAXN];   //fail指针,匹配失败时返回位置
    int cnt[MAXN];    //cnt数组表示以该节点结束的字符串数量
    int root,tot;     //root是根节点,tot标记节点序号

    int newnode(){
        for(int i=0;i<son_num;i++)
            tree[tot][i] = -1;
        cnt[tot++] = 0;
        return tot-1;     //返回当前节点编号
    }

    void init(){
        tot = 0;
        root = newnode();
    }

    int get_id(char c){  //返回儿子节点编号,注意修改
        return c-'a';
    }

    void Insert(char *s){
        int len = strlen(s);
        int now = root;
        for(int i=0;i<len;i++){
            int id = get_id(s[i]);
            if(tree[now][id]==-1)   //无后继节点,新建节点
                tree[now][id] = newnode();
            now = tree[now][id];
        }
        cnt[now]++;
    }

    void build(){       //建立fail数组,构造失配指针
        queue<int>q;    //bfs寻找
        fail[root] = root;  //根节点的fail直接指向自己
        for(int i=0;i<son_num;i++){
            if(tree[root][i]==-1)
                tree[root][i] = root;
            else{           //根节点儿子的fail指针指向根节点
                fail[tree[root][i]]=root;
                q.push(tree[root][i]);
            }
        }
        while(!q.empty()){
            int now = q.front();
            q.pop();
            for(int i=0;i<son_num;i++){    //构造该节点的所有儿子fail指针
                if(tree[now][i]==-1)
                    tree[now][i] = tree[fail[now]][i];   //该段的最后一个节点匹配后,跳到拥有最大公共后缀的fail节点继续匹配
                else{
                    fail[tree[now][i]] = tree[fail[now]][i];   //当前节点的fail节点等于它前驱节点的fail节点的后继节点
                    q.push(tree[now][i]);
                }
            }
        }
    }

    int query(char *s){
        int len = strlen(s);
        int now = root;
        int ans = 0;
        for(int i=0;i<len;i++){
            int id = get_id(s[i]);
            now = tree[now][id];
            int tmp = now;
            while(tmp != root){
                ans+=cnt[tmp];   //加上以当前节点结尾的字符串数
                cnt[tmp] = 0;    //可防止计算重复的字符串
                tmp = fail[tmp]; //每次找最大公共后缀对应的fail节点
            }
        }
        return ans;
    }

    void debug(){
        for(int i = 0;i < tot;i++){
            printf("id = %3d,fail = %3d,cnt = %3d,chi = [",i,fail[i],cnt[i]);
            for(int j = 0;j < son_num;j++)
                printf("%2d",tree[i][j]);
            printf("]\n");
        }
    }
}ac;

 

指针模拟代码:

#define son_num 26   //儿子节点数量,注意修改
struct node{
    int terminal;   //结束位置数量
    node *fail;
    node *Next[son_num];
    node(){
        fail=NULL;
        terminal=0;    //记录结束位置
        for(int i=0;i<son_num;i++)
            Next[i] = NULL;
    }
};

int get_id(char c){  //这里注意修改
    return c-'a';
}

void Insert(node *root,char *str){//x为该病毒的编号
    node *p=root;
    int len = strlen(str);
    for(int i=0;i<len;i++){
        int index=get_id(str[i]);
        if(p->Next[index]==NULL)
            p->Next[index]=new node();
        p=p->Next[index];
    }
    p->terminal++;
}

//寻找失败指针
void build_fail(node *root){
    queue <node *> que;
    root->fail=NULL;
    que.push(root);
    while(!que.empty()){
        node *temp=que.front();
        que.pop();
        node *p=NULL;
        for(int i=0;i<son_num;i++){
            if(temp->Next[i]!=NULL){
                if(temp==root) temp->Next[i]->fail=root;
                else{
                    p=temp->fail;
                    while(p!=NULL){
                        if(p->Next[i]!=NULL){
                            temp->Next[i]->fail=p->Next[i];
                            break;
                        }
                        p=p->fail;
                    }
                    if(p==NULL)
                        temp->Next[i]->fail=root;
                }
                que.push(temp->Next[i]);
            }
        }
    }
}

//询问主串中含有多少个关键字
int query(node *root,char *str){
    int cnt=0;
    int len=strlen(str);
    node *p=root;
    for(int i=0;i<len;i++){
        int index=get_id(str[i]);
        while(p->Next[index]==NULL&&p!=root)
            p=p->fail;
        p=p->Next[index];
        if(p==NULL) p=root;
        node *temp=p;
        while(temp!=root){
            cnt+=temp->terminal;
            temp->terminal = 0;     //防止重复加字符串
            temp=temp->fail;
        }
    }
    return cnt;
}

//指针处理
void deal(node *now){
    if(now == NULL)
        return ;
    for(int i=0;i<son_num;i++){
        if(now->Next[i]!=NULL)
            deal(now->Next[i]);
    }
    delete now;
}
//node *root = new node();  //建立根节点

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值