ac自动机

https://blog.csdn.net/bestsort/article/details/82947639
https://www.cnblogs.com/cmmdc/p/7337611.html

关于ac自动机,原理还没有很懂,我先就目前的理解说一说我理解的原理,等更彻底的理解之后再来修改补充。

在匹配过程中,如果找到了一个模式串,就把这个串的fail指针指向的其所有的后缀串全部计算进去(应该是这样。。),如果失配了,也要找当前串的存在的后缀串并计算进去

关于建立Trie树,很简单,但是要记录一下这个单词在建立的时候有几次

fail指针,采用bfs的方法:(关于手写队列和stl都可以)
1、第一层节点要特殊处理,使第一层节点的fail指针全部指向根节点,然后将第一层节点全部加入队列
2、一个节点出队列,然后将其存在的子节点的fail指针指向父节点的fail指针指向的节点的具有相同子节点的节点((有点绕是吧。。)可以看一下上面第一篇文章,里面的图片说明很直观容易理解,)但是要注意,如果父节点fail指向的节点子节点没有与之相同的节点,那就按照这个规律继续向上去找,如果到根节点都找不到,就说明不存在,那么就将这个节点的fail指针直接指向root
3、每一层都重复这个操作,直到Trie的所有节点全部都建立了fail指针

hdu 2222 Keywords Search

//AC自动机
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <string>
#include <sstream>

using namespace std;

const int Size = 26;
const int MAXL = 55;
const int MAXl  = 1000000 + 5;

struct Trie
{
    int count;//前缀出现次数;
    int sum;//这个节点是不是一个单词的结尾,并记录这个单词在建树的时候出现了几次
    Trie *next[Size];//孩子节点数目
    Trie *fail;
    //bool isEnd;//判断到该点时,是否是一个单词
    //string name;
};

char pat[MAXL],text[MAXl];
int ans;

Trie *root;//建立根节点;

void init(Trie *node)
{
    node->count = node->sum = 0;
    memset(node->next,NULL,sizeof node->next);
    node->fail = NULL;
    //node->isEnd = false;
}

void Insert(const char *s)
{
    int len = strlen(s);
    int pos;
    Trie *p = root;
    for(int i = 0; i < len; i++){
        pos = s[i] - 'a';
        if(p->next[pos] == NULL){
            //如果这个字符在这个节点没有储存过,就建立一个新节点来储存
            p->next[pos] = new Trie;
            init(p->next[pos]);
        }
        p = p->next[pos];
        p->count++;
    }
    p->sum++;//表明当前位置为一个单词的节点
}

void build_fail_pointer()
{
    queue <Trie *> q;
    Trie *p = root;
    //将第一层存在的节点全部指向root节点
    for(int i = 0; i < Size; i++){
        if(p->next[i] != NULL){
            p->next[i]->fail = root;
            q.push(p->next[i]);
        }
    }
    while(!q.empty()){
        Trie *temp = q.front();
        q.pop();
        //依次回溯该节点的父节点的失败指针直到某节点的next[i]与该节点相同,
        //则把该节点的失败指针指向该next[i]节点;
        //若回溯到 root 都没有找到,则该节点的失败指针指向 root
        for(int i = 0; i < Size; i++){
            if(temp->next[i] != NULL){
                Trie *node = temp->fail;//将该结点的父结点的失败指针给node
                while(node != NULL){
                    if(node->next[i] != NULL){
                        temp->next[i]->fail = node->next[i];
                        break;
                    }
                    node = node->fail;
                }
                if(node == NULL){
                    //如果这个节点的fail指针匹配失败,那么就指向根节点
                    temp->next[i]->fail = root;
                }
                q.push(temp->next[i]);
            }
        }
    }
}

void find_ac_automaton()
{
    Trie *p = root;
    int pos;
    int len = strlen(text);
    for(int i =0; i < len; i++){
        pos = text[i] - 'a';
        while(p->next[pos] == NULL && p != root){
        //如果失配且下一个fail节点不是根节点,就往上找
            p = p->fail;
        }
        //找到上级节点的fail指针指向的节点是否含有当前字符
        p = p->next[pos];
        if(p == NULL){
            //如果没找到,就返回根节点从新开始找
            p = root;
        }
        Trie *tep = p;
        //向上查找fail指针指向的字符串,这些字符串的结尾都是这个字符(个人理解)
        //并把出现的模式串数量全部加进去
        while(tep != root && tep->sum != -1){
            ans += tep->sum;
            tep->sum = -1;//防止重复计算
            tep = tep->fail;
        }
    }
}

void delTrie(Trie *root)
{
    for(int i = 0; i < Size; i++){
        if(root->next[i] != NULL){
            delTrie(root->next[i]);
        }
    }
    delete root;
}

int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        root = new Trie;
        init(root);
        while(n--){
            scanf("%s",pat);
            Insert(pat);
        }
        scanf("%s",text);
        build_fail_pointer();
        ans = 0;
        find_ac_automaton();
        printf("%d\n",ans);
        delTrie(root);
    }
    return 0;
}

hdu 5880
这个题,咋说呢,简单模板,但是做起来就跟神仙题一样,究极离谱(给自己的菜找借口),指针方法内存存爆了,也不知道为啥爆了,先贴一下吧,顺便学学数组的ac自动机操作。。
如果用指针内存爆了的问题解决了,再说这个代码是不是一定正确吧(狗头)

//AC自动机
//指针
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <map>
#include <string>
#include <sstream>

using namespace std;

const int Size = 26;
const int MAXN = 1e6 + 10;

struct Trie
{
    //int count;//前缀出现次数;
    int sum;//这个节点是不是一个单词的结尾,并记录这个单词在建树的时候出现了几次
    Trie *next[Size];//孩子节点数目
    Trie *fail;
    //bool isEnd;//判断到该点时,是否是一个单词
    //string name;
}trie[MAXN];

char pat[MAXN],text[MAXN];
int cnt = 0;

Trie *root;//建立根节点;

void init(Trie *node)
{
    node->sum = 0;
    memset(node->next,NULL,sizeof node->next);
    node->fail = NULL;
    //node->isEnd = false;
}

Trie *Newndoe()
{
    Trie *p = &trie[cnt++];
    init(p);
    return p;
}

void Insert(const char *s)
{
    int len = strlen(s);
    int pos;
    Trie *p = root;
    for(int i = 0; i < len; i++){
        pos = s[i] - 'a';
        if(p->next[pos] == NULL){
            //如果这个字符在这个节点没有储存过,就建立一个新节点来储存
            p->next[pos] = Newndoe();
        }
        p = p->next[pos];
    }
    p->sum = len;//记录模式串长度,方便转变成'*'
}

void build_fail_pointer()
{
    queue <Trie *> q;
    Trie *p = root;
    //将第一层存在的节点全部指向root节点
    for(int i = 0; i < Size; i++){
        if(p->next[i] != NULL){
            p->next[i]->fail = root;
            q.push(p->next[i]);
        }
    }
    while(!q.empty()){
        Trie *temp = q.front();
        q.pop();
        //依次回溯该节点的父节点的失败指针直到某节点的next[i]与该节点相同,
        //则把该节点的失败指针指向该next[i]节点;
        //若回溯到 root 都没有找到,则该节点的失败指针指向 root
        for(int i = 0; i < Size; i++){
            if(temp->next[i] != NULL){
                Trie *node = temp->fail;//将该结点的父结点的失败指针给node
                while(node != NULL){
                    if(node->next[i] != NULL){
                        temp->next[i]->fail = node->next[i];
                        break;
                    }
                    node = node->fail;
                }
                if(node == NULL){
                    //如果这个节点的fail指针匹配失败,那么就指向根节点
                    temp->next[i]->fail = root;
                }
                q.push(temp->next[i]);
            }
        }
    }
}

void find_ac_automaton()
{
    Trie *p = root;
    int pos;
    int len = strlen(text);
    for(int i =0; i < len; i++){
        if(text[i] >= 'A' && text[i] <= 'Z'){
            pos = text[i] - 'A';
        }
        else if(text[i] >= 'a' && text[i] <= 'z'){
            pos = text[i] - 'a';
        }
        else{
            continue;
        }
        while(p->next[pos] == NULL && p != root){
        //如果失配且下一个fail节点不是根节点,就往上找
            p = p->fail;
        }
        //找到上级节点的fail指针指向的节点是否含有当前字符
        p = p->next[pos];
        if(p == NULL){
            //如果没找到,就返回根节点从新开始找
            p = root;
        }
        Trie *tep = p;
        //向上查找fail指针指向的字符串,这些字符串的结尾都是这个字符
        //并把出现的模式串数量全部加进去
        while(tep != root){//需要把所有出现的模式串都和谐掉,所以不需要去重操作
            if(tep->sum){
                for(int k = i; k > i - tep->sum; k--){
                    text[k] = '*';
                }
                break;//第一个后缀一定是最长的,不需要再转移到下一个fail指针
            }
            tep = tep->fail;
        }
    }
}

/*void delTrie(Trie *root)
{
    for(int i = 0; i < Size; i++){
        if(root->next[i] != NULL){
            delTrie(root->next[i]);
        }
    }
    delete root;
}*/

int main()
{
    int t,n;
    scanf("%d",&t);
    while(t--){
        cnt = 0;
        scanf("%d",&n);
        root = Newndoe();
        while(n--){
            scanf("%s",pat);
            Insert(pat);
        }
        cin.get();
        gets(text);
        build_fail_pointer();
        find_ac_automaton();
        puts(text);
        /*for(int i = 0; i < cnt; i++){
            trie[i].sum = 0;
            for(int j = 0; j < Size; j++){
                trie[i].next[j] = NULL;
            }
            trie[i].fail = NULL;
        }*/
    }
    return 0;
}

对于数组的建立fail指针,为什么一步就可以呢?因为对于没有出现的字符,它会把父节点fail指针指向的节点的与其字符相同的子节点存起来,这样就省略的如果当前节点存在,但是其父节点fail指针指向的节点与之相同字符的子节点不存在,就会向上找的过程,因为上层节点在建立fail指针的时候,已经给你预处理好了,所以只需要直接取父节点fail指针指向的节点的具有相同字符的值就可以,如果其值是指向根节点的,那就说明,当前要建立fail指针的节点,也指向根节点

//hdu 5880
//数组,ac自动机
#include <iostream>
#include <string>
#include <cstring>
#include  <cmath>
#include <cstdio>
#include <cstdlib>
#include <map>
#include <queue>

using namespace std;

const int MAXN = 1e6 + 10;
const int Size = 26;

int fail[MAXN],trie[MAXN][Size],val[MAXN],nNode;
//失败指针,字典树(数组实现),记录模式串长度,子典树节点
char s[MAXN];

int tran(char ch)
{
        return tolower(ch) - 'a';
}

int newNode()
{
        for(int i = 0; i < Size; i++){
                trie[nNode][i] = 0;
                fail[i] = 0;
        }
        val[nNode] = 0;
        return nNode++;
}

void Insert(const char *s)
{
        int u = 0;
        int len = strlen(s);
        for(int i = 0; i < len; i++){
                if(!trie[u][tran(s[i])]){
                        trie[u][tran(s[i])] = newNode();
                }
                u = trie[u][tran(s[i])];
        }
        //在结尾处记录模式串长度
        val[u] = len;
}

void build_ac_fail_pointer()
{
        queue <int> q;
        fail[0]  = 0;
        for(int  i = 0; i < Size; i++){
                if(trie[0][i]){
                        fail[trie[0][i]] = 0;//第一层fail指针指向根结点
                        q.push(trie[0][i]);
                }
                else{
                        fail[trie[0][i]] = 0;
                }

        }
        while(!q.empty()){
                int u = q.front();
                q.pop();
                for(int i = 0; i < Size; i++){
                        if(trie[u][i]){
                                fail[trie[u][i]] = trie[fail[u]][i];
                                q.push(trie[u][i]);
                        }
                        else{
                                //向上找上级,找到的第一个用fail指针指向,但是如果没有上级满足
                                //那么肯定其父节点fail指针的  ℹ  节点对应的值为0,就不用向指针方法一样不断向上找了
                                //上面同理
                                trie[u][i] = trie[fail[u]][i];
                        }
                }
        }
}

//替换字符
void Query(char *s)
{
        int len = strlen(s);
        int u = 0;
        for(int i = 0; i <  len; i++){
                //判断是否为字符,是则返回非0不是返回0
                if( !isalpha ( s[i] ) ){
                        //如果不是字符,那么就从根结点从新开始找
                        u = 0;
                        continue;
                }
                //如果是字符,那么就直接取最大后缀,如果没有不存在,那么长度肯定为0
                u = trie[u][tran(s[i])];
                for(int j = u;  j != 0;  j = fail[j]){
                        if(val[j]){
                                for(int k = i; k > i - val[j]; k--){
                                        s[k] = '*';
                                }
                        }
                }
        }
}

int main()
{
        int t;
        scanf("%d",&t);
        while(t--){
                nNode = 0;
                newNode();
                int n;
                scanf("%d",&n);
                while(n--){
                        scanf("%s",s);
                        Insert(s);
                }
                build_ac_fail_pointer();
                getchar();
                //linux下不能使用gets。。使用fgets替代,但是不知道为啥就tle了
                //cin.get(s,MAXN );
               // fgets(s,MAXN -  5,stdin);
                gets(s);
                Query(s);
                puts(s);
        }
        return 0;
}

洛谷3808
模板题

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <cmath>
#include <cstdlib>

using namespace std;

const int MAXN = 1000005;
const int Size = 26;

int trie[MAXN][Size],fail[MAXN * Size],cnt[MAXN],nNode;
int ans;
string s;

int newnode()
{
	for(int i = 0; i < Size; i++){
		trie[nNode][i] = 0;
	}
	fail[nNode] = 0;
	cnt[nNode] = 0;
	return nNode++;
}

void Insert(const string  &s)
{
	int len = s.size();
	int u = 0;
	for(int i = 0; i < len; i++){
		int pos = s[i] - 'a';
		if( !trie[u][pos] ){
			trie[u][pos] = newnode();
		} 
		u = trie[u][pos];
	}
	//记录该字符串出现次数
	cnt[u]++;
}

void build_ac_fail_pointer()
{
	queue <int> q;
	fail[0] = 0;
	for(int i = 0; i < Size; i++){
		if( trie[0][i] ){
			fail[trie[0][i]] = 0;
			q.push(trie[0][i]);
		}
		else{
			fail[trie[0][i]] = 0;
		}
	}
	while ( !q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i= 0; i < Size; i++){
			if( trie[u][i] ){
				fail[trie[u][i]] = trie[fail[u]][i];
				q.push(trie[u][i]);
			}
			else{
				trie[u][i]  = trie[fail[u]][i];
			}
		}
	}	
}

void Query(const string &s)
{
	int len = s.size();
	int u = 0;
	for(int i = 0; i < len; i++){
		int pos = s[i] - 'a';
		u = trie[u][pos];
		for(int j = u; j && cnt[j] != -1; j = fail[j]){
			ans += cnt[j];
			cnt[j] = -1;
		}
	}
}

int main()
{
	int n;
	cin>>n;
	newnode();//很重要,因为根节点是0,如果没有这一步,插入的时候,会从根节点新建节点
	while(n--){
		cin>>s;
		Insert(s);
	}
	build_ac_fail_pointer();
	cin>>s;
	Query(s);
	cout<<ans<<endl;
	return 0;
}

洛谷3796
模板题,先把输入的字符按照输入的顺序编号(建议不要从0开始编号,个人认为会比较麻烦)
然后在建树的时候,在字符串尾部位置标记上我们给定的序号
在扫描模式串的时候,只要这个序号出现一次,那么相应的字符串出现次数则加一,最后求出出现次数最多的字符串即可

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <string>
#include <cmath>
#include <cstdlib>

using namespace std;

const int MAXN = 1000005;
const int Size = 26;

int trie[MAXN][Size],fail[MAXN],cnt[MAXN],nNode,vis[MAXN];
string s[151],T;

void init()
{
	memset(trie,0,sizeof(trie));
	memset(fail,0,sizeof(fail));
	memset(cnt,0,sizeof(cnt));
	memset(vis,0,sizeof(vis));
	nNode  = 1;
}

int newnode()
{
	for(int i = 0; i < Size; i++){
		trie[nNode][i] = 0;
	}
	fail[nNode] = 0;
	cnt[nNode] = 0;
	return nNode++;
}

void Insert(const string  &s,int i)
{
	int len = s.size();
	int u = 0;
	for(int i = 0; i < len; i++){
		int pos = s[i] - 'a';
		if( !trie[u][pos] ){
			trie[u][pos] = newnode();
		} 
		u = trie[u][pos];
	}
	//记录该字符串的标号
	cnt[u] = i;
}

void build_ac_fail_pointer()
{
	queue <int> q;
	fail[0] = 0;
	for(int i = 0; i < Size; i++){
		if( trie[0][i] ){
			fail[trie[0][i]] = 0;
			q.push(trie[0][i]);
		}
		else{
			fail[trie[0][i]] = 0;
		}
	}
	while ( !q.empty())
	{
		int u = q.front();
		q.pop();
		for(int i= 0; i < Size; i++){
			if( trie[u][i] ){
				fail[trie[u][i]] = trie[fail[u]][i];
				q.push(trie[u][i]);
			}
			else{
				trie[u][i]  = trie[fail[u]][i];
			}
		}
	}	
}

void Query(const string &s)
{
	int len = s.size();
	int u = 0;
	for(int i = 0; i < len; i++){
		int pos = s[i] - 'a';
		u = trie[u][pos];
		for(int j = u; j; j = fail[j]){
			if(cnt[j]){
				vis[cnt[j]]++;//如果有模式串标记,更新出现次数
			}
		}
	}
}
 
int main()
{
	int n;
	while(cin>>n,n){
		init();
		//newnode();//很重要,因为根节点是0,如果没有这一步,插入的时候,会从根节点新建节点
		for(int i = 1; i <= n; i++){
			cin>>s[i];
			Insert(s[i],i);//利用输入字符串的顺序来给字符串标序,也可以用map
		}
		build_ac_fail_pointer();
		cin>>T;
		Query(T);
		int ans = 0;
		for(int i = 1; i <= n; i++){
			ans = max(ans,vis[i]);
		}
		cout<<ans<<endl;
		for(int i = 1; i <= n; i++){
			if(vis[i] == ans){
				cout<<s[i]<<endl;
			}
		}
	}
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值