首先结构体的建立
struct node
{
node *fail;// fail 指针
node *nex[26];//字典树标配,26个字母
int cnt;//是否为该单词的最后一个节点
node()//构建函数
{
fail = NULL;
for(register int i=0;i<26;i++)nex[i]=NULL;
cnt = 0;
}
}q[maxn];//队列,方便用于bfs构造失败指针
int head,tail; // 队列头尾
char key[100],s[maxn];//查找词和模板串
构建完成字典树后图片如下
之后就是插入函数。。。
inline void insert(char *s, node *rt)
{
node *p = rt;
int i=0, index;
while(s[i])
{
index = s[i] - 'a';//找到字母编号
if(p->nex[index] == NULL)p->nex[index]=new node();
p = p->nex[index];
i ++;
}
p->cnt ++;//在单词的最后一个节点count+1,代表一个单词
}
在构造完这棵Tire之后,接下去的工作就是构造下失败指针。构造失败指针的过程概括起来就一句话:
设这个节点上的字母为C,沿着他父亲的失败指针走,直到走到一个节点,他的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母也为C的儿子。如果一直走到了root都没找到,那就把失败指针指向root。具体操作起来只需要:先把root加入队列(root的失败指针指向自己或者NULL),这以后我们每处理一个点,就把它的所有儿子加入队列,队列为空。
--------摘自http://www.cppblog.com/mythit/archive/2009/04/21/80633.html
非常的像kmp的 next 数组,这里就fail指针也可以理解成,如果单词匹配到这里失败了,返回最近使用过当前字母 c 的节点,这样就可以继续去匹配了。。。缩短时间了,。。
inline void build(node *rt)
{
rt->fail = NULL;//根节点的 fail 为空
q[head++] = rt;//入队
while(head != tail)//用 bfs 思路不断找到最近的fail
{
node *temp = q[tail++], *p = NULL;//取队首
for(register int i=0;i<26;i++)
{
if(temp->nex[i] != NULL)//如果这个字母出现过
{
if(temp == rt)temp->nex[i]->fail = rt;
else
{
p = temp->fail;
while(p != NULL)
{
if(p->nex[i] != NULL)
{
temp->nex[i]->fail = p->nex[i];
break;
}
}
if(p == NULL)temp->nex[i]->fail = rt;
}
q[head++] = temp->nex[i];
}
}
}
}
从代码观察下构造失败指针的流程:对照图-2来看,首先root的fail指针指向NULL,然后root入队,进入循环。第1次循环的时候,我们需要处理2个节点:root->next[‘h’-‘a’](节点h) 和 root->next[‘s’-‘a’](节点s)。把这2个节点的失败指针指向root,并且先后进入队列,失败指针的指向对应图-2中的(1),(2)两条虚线;第2次进入循环后,从队列中先弹出h,接下来p指向h节点的fail指针指向的节点,也就是root;进入第13行的循环后,p=p->fail也就是p=NULL,这时退出循环,并把节点e的fail指针指向root,对应图-2中的(3),然后节点e进入队列;第3次循环时,弹出的第一个节点a的操作与上一步操作的节点e相同,把a的fail指针指向root,对应图-2中的(4),并入队;第4次进入循环时,弹出节点h(图中左边那个),这时操作略有不同。在程序运行到14行时,由于p->next[i]!=NULL(root有h这个儿子节点,图中右边那个),这样便把左边那个h节点的失败指针指向右边那个root的儿子节点h,对应图-2中的(5),然后h入队。以此类推:在循环结束后,所有的失败指针就是图-2中的这种形式。
最后,我们便可以在AC自动机上查找模式串中出现过哪些单词了。匹配过程分两种情况:(1)当前字符匹配,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;(2)当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
简单总结一下就是,完全模拟kmp,如果当前匹配失败了,返回最近的重合度最高的前缀部分,当然如果连续回退到根节点,那么显而易见,匹配失败了。。。。你看这个图里很显然, he这个小的字符串重复出现了,搜索的时候可以利用这点。
inline int query(node *rt)
{
int i=0,ct=0,index,len=strlen(s);
node *p = rt;
while(s[i])
{
index = s[i]-'a';
while(p->nex[index]==NULL && p!=rt) p=p->fail;
p = p->nex[index];
p = p==NULL ? rt:p;
node *temp = p;
while(temp!=rt && temp->cnt!=-1)
{
ct += temp->cnt;
temp->cnt = -1;
temp = temp->fail;
}
i++;
}
return ct;
}
附加一个优秀博客地址 https://blog.youkuaiyun.com/creatorx/article/details/71100840