学习之后就要做好笔记嘛,PS:感冒好了之后,脑袋灵光了不少啊。。。。
前几天学习了KMP算法,又复习了字典树,都是为了AC-自动机,这个神奇的东西,KMP是单字符串匹配,而AC-自动机是在Tire树的情况下,进行的多字符匹配的,关键在于next数组的求解,只要理解了next数组的意义呢,AC自动机也就好搞了,首先贴下两篇关于AC自动机的学习资料
http://blog.csdn.net/niushuai666/article/details/7002823
http://hi.baidu.com/nialv7/item/ce1ce015d44a6ba7feded52d
这两篇博文都是非常好的关于自动机匹配函数的理解,其实自动机由建树,建立匹配函数,加查找这三部分构成。
AC自动机的构造:
1.构造一棵Trie,作为AC自动机的搜索数据结构。
2.构造fail指针,使当前字符失配时跳转到具有最长公共前后缀的字符继续匹配。如同 KMP算法一样, AC自动机在匹配时如果当前字符匹配失败,那么利用fail指针进行跳转。由此可知如果跳转,跳转后的串的前缀,必为跳转前的模式串的后缀并且跳转的新位置的深度(匹配字符个数)一定小于跳之前的节点。所以我们可以利用 bfs在 Trie上面进行 fail指针的求解。
3.扫描主串进行匹配。
直接摘录下来了,这就是自动机的具体构造方法。然后我再贴下,我按照kuangbin巨巨写下的模板,并且对模板进行详细的解释。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll __int64
#define MAX 1000009
const int maxnode = MAX;
const int sigma_size = 26;
using namespace std;
struct Trie
{
int ch[maxnode][sigma_size];
int val[maxnode];
int next[maxnode];
int sz,root;
int idx(char c)//编号函数
{
return c - 'a';
}
int newnode()//建立新的结点,跟以前写的不一样,但是我觉得这种写法更好一些
{
for(int i = 0; i<26; i++)
ch[sz][i] = 0;
val[sz++] = 0;
return sz - 1;
}
void init()//初始化
{
sz = 0;
root = newnode();
}
void Insert(char* s)//插入
{
int u = root;
int n = strlen(s);
for(int i = 0; i<n; i++)
{
int c = idx(s[i]);
if(!ch[u][c])
ch[u][c] = newnode();
u = ch[u][c];
}
val[u]++;
}
/*
Trie树上的失败指针与KMP的next数组类似:
假设有一个节点k,他的失败指针指向j。那么k,j满足这个性质:设root到j的距离为n,
则从k之上的第n个节点到k所组成的长度为n的单词,与从root到j所组成的单词相同。
建立失配数组:
对于每个节点,我们可以这样处理:设这个节点上的字母为C,沿着他父亲的失败指针走,
直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字目也为C的儿子。如果一直走到了root都没找到,
那就把失败指针指向root最开始,我们把root加入队列(root的失败指针显然指向自己),
这以后我们每处理一个点,就把它的所有儿子加入队列,直到搞完。
DarkRaven原创
匹配过程分两种情况:
(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
*/
void build()//建立next数组
{
queue<int>Q;
next[root] = root;
for(int i = 0; i<26; i++)
{
if(!ch[root][i])
{
ch[root][i] = root;
}
else
{
next[ch[root][i]] = root;;//第一个字符不需要匹配,所以都指向root
Q.push(ch[root][i] );//先入队rppt
}
}
while(!Q.empty())
{
int now = Q.front();//取队首
cout<<now<<endl;
Q.pop();
for(int i = 0; i<26; i++)
{
if(!ch[now][i])
{
ch[now][i] = ch[next[now]][i];//
}
else
{
next[ch[now][i]] = ch[next[now]][i];
Q.push(ch[now][i]);
}
}
}
}
int query(char* s)//扫描查询
{
int len = strlen(s);
int now = root;
int res = 0;
for(int i = 0; i<len; i++)
{
int c = idx(s[i]);
now = ch[now][c];
int temp = now;
while(temp!= root)
{
res+=val[temp];
val[temp] = 0;
temp = next[temp];
}
}
return res;
}
void debug()//找bug
{
for(int i = 0; i<sz; i++)
{
printf("id = %3d,next = %3d,val = %3d,chi = [",i,next[i],val[i]);
for(int j = 0; j < 26; j++)
printf("%2d",ch[i][j]);
printf("]\n");
}
}
};
char str[MAX];
Trie Ac;
int main()
{
int T;
int n;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
Ac.init();
for(int i = 0; i<n; i++)
{
scanf("%s",str);
Ac.Insert(str);
}
Ac.build();
scanf("%s",str);
// Ac.debug();
printf("%d\n",Ac.query(str));
}
return 0;
}