AC自动机 算法 及模板

算法的详细讲解,请参考

http://www.cppblog.com/mythit/archive/2009/04/21/80633.html

http://www.cnblogs.com/destinydesigner/archive/2009/10/15/1584191.html 总结性讲解

推荐 AC自动机算法的 模板题 hdu 2222

 

1.字典树Trie
建立一个字典树,作用在于压缩信息,更容易求得公共前缀。

2.失配指针
失配指针在于高效地更新公共前缀,利用其中的信息。由BFS的性质以及Trie树的性质可知,若某一序列s[1...m]在m+1处失配时,则该序列更新为s[i...m](i >= 2 && i <= m)。

3.AC自动机
根据之前构建的适配指针,搜索与主串相匹配的模式字符。类似kmp,主串指针一直前进,改变模式串的指针

位置来保证,主串之前的匹配依然成立。

 

对于一个Trie树,建立失配指针后,Trie树会具有一些特殊的性质:

首先声明几个重要的指针。

1)指针p。指向当前已匹配的字符。若p指向root,则当前匹配的字符序列为空。
2)指针p->fail。指向与p有相同字符的节点,即p的失配指针。
3)指针temp。

对于Trie树中的一个节点,对应一个序列s[1...m]。此时,p指向字符s[m]。若在下一个字符处失配,即p->next[s[m+1]] == NULL,则由失配指针跳到另一个节点(p->fail)处,该节点对应的序列为s[i...m]。若继续失配,则序列依次跳转直到序列为空或出现匹配。在此过程中,p的值一直在变化,但是p对应节点的字符没有发生变化。在此过程中,我们观察可知,最终求得得序列s则为最长公共后缀。另外,由于这个序列是从root开始到某一节点,则说明这个序列有可能是某些序列的前缀。

再次讨论p指针转移的意义。如果p指针在某一字符s[m+1]处失配(即p->next[s[m+1]] == NULL),则说明没有单词s[1...m+1]存在。此时,如果p的失配指针指向root,则说明当前序列的任意后缀不会是某个单词的前缀。如果p的失配指针不指向root,则说明序列s[i...m]是某一单词的前缀,于是跳转到p的失配指针,以s[i...m]为前缀继续匹配s[m+1]。

对于已经得到的序列s[1...m],由于s[i...m]可能是某单词的后缀,s[1...j]可能是某单词的前缀,所以s[1...m]中可能会出现单词。此时,p指向已匹配的字符,不能动。于是,令temp = p,然后依次测试s[1...m], s[i...m]是否是单词。

Keywords Search

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 19147    Accepted Submission(s): 6402


Problem Description
In the modern time, Search engine came into the life of everybody like Google, Baidu, etc.
Wiskey also wants to bring this feature to his image retrieval system.
Every image have a long description, when users type some keywords to find the image, the system will match the keywords with description of image and show the image which the most keywords be matched.
To simplify the problem, giving you a description of image, and some keywords, you should tell me how many keywords will be match.
 


 

Input
First line will contain one integer means how many cases will follow by.
Each case will contain two integers N means the number of keywords and N keywords follow. (N <= 10000)
Each keyword will only contains characters 'a'-'z', and the length will be not longer than 50.
The last line is the description, and the length will be not longer than 1000000.
 


 

Output
Print how many keywords are contained in the description.
 


 

Sample Input
1 5 she he say shr her yasherhs
 


 

Sample Output
3
 
 
题目大意:
 
几组测试数据,每组数据给定 n 个单词 如上面的实例 n=5 : say she shr he her ,然后给定一个字符串 yasherhs 。 问一共有多少单词在这个字符串中出现过。

修改后,正确的模板代码:

 

//=+
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define kind 26
const int MAXN = 10000000;
struct node
{
    int count; //是否为单词最后一个节点
    node *next[26];//Trie每个节点的26个子节点
    node *fail; //失败指针
};
node *q[MAXN]; //队列,采用bfs 构造失败指针
char keyword[55];//输入单词 模式串
char str[1000010];// 需要查找的 主串
int head,tail;//队列 头尾指针
node *root;
void insert(char *word,node *root)
{
     int index,len;
     node *p = root,*newnode;
     len = strlen(word);
     for(int i=0 ;i < len ; i++ )
     
         index=word[i]-'a';
         if(!p->next[index])//该字符节点不存在,加入Trie树中
         {
           // 初始化 newnode 并 加入 Trie 树
            newnode=(struct node *)malloc(sizeof(struct node));    
            for(int j=0;j<26;j++) newnode->next[j]=0;
            newnode->count=0;newnode->fail=0;
            p->next[index]=newnode;
         }
         p=p->next[index];//指针移动至下一层
     }
     p->count++;  //单词结尾 节点 count + 1 做标记  
}
void build_ac_automation(node *root)
{
     head=0;tail=1;
     q[head]=root;
     node *temp,*p;
     while(head<tail)//bfs构造 Trie树的失败指针
     {
       //算法类似 kmp ,这里相当于得到 next[]数组
       //重点在于,匹配失败时,由fail指针回溯到正确的位置
       
        temp=q[head++];
         for(int i=0;i< 26 ;i ++)
         {
             if(temp->next[i])//判断实际存在的节点
             {
                 // root 下的第一层 节点 都 失败指针都 指向root
                 if(temp==root)temp->next[i]->fail=root;
                 else {
                    //依次回溯 该节点的父节点的失败指针
                   //知道某节点的next[i]与该节点相同,则
                   //把该节点的失败指针指向该next[i]节点
                   //若回溯到 root 都没有找到,则该节点
                   //的失败指针 指向 root
                  
                    p=temp->fail;//temp 为节点的父指针
                    while(p){
                       if(p->next[i]){
                       temp->next[i]->fail=p->next[i];
                       break;
                       }
                       p=p->fail;
                    }
                    if(!p)temp->next[i]->fail=root;
                 }
                 //每处理一个点,就把它的所有儿子加入队列,           
                 //直到队列为空
                 q[tail++]=temp->next[i];
             }
                       
     }
}
int query(node *root)//类似于 kmp算法。
{//i为主串指针,p为匹配串指针
    int i,cnt=0,index,len=strlen(str);
    node *p=root;
    for(i=0; i < len ;i ++)
    {
       index=str[i]-'a';
      //由失败指针回溯寻找,判断str[i]是否存在于Trie树种
       while( !p->next[index] && p != root)p=p->fail;
       p=p->next[index];//找到后 p 指向该节点
     
       //指针回为空,则没有找到与之匹配的字符
      
       if(!p)p=root;//指针重新回到根节点root,下次从root开始搜索Trie树
      
       node *temp=p;//匹配该节点后,沿其失败指针回溯,判断其他节点是否匹配
      
       while(temp != root )//匹配 结束控制
       {
           if(temp->count>=0)//判断 该节点是否被访问
           {
              //统计出现的单词个数cnt,由于节点不是单词结尾时count为0,
             //故 cnt+=temp->count; 只有 count >0时才真正统计了单词个数
            
             cnt+=temp->count;
              temp->count=-1; //标记已访问
           }
           else break;//节点已访问,退出循环
           temp=temp->fail;//回溯失败指针继续寻找下一个满足条件的节点     
       }
    }
    return cnt;
}
int main()
{
    int i,t,n,ans;
    scanf("%d",&t);
    while(t--)
    {
       root=(struct node *)malloc(sizeof(struct node));
       for(int j=0;j<26;j++) root->next[j]=0;
       root->fail=0;
       root->count=0;
       scanf("%d",&n);
       getchar();
       for(i=0;i<n;i++)
       {
           gets(keyword);
           insert(keyword,root);
       }
       build_ac_automation(root);
       gets(str);
       ans=query(root);
       printf("%d\n",ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值