HDU 2222 Keywords Search

发现字符串处理方面的基础很薄弱,于是这两天打算加强一下,去做了一个AC自动机的题。在做AC自动机之前,先做了一个trie树的题练手,做好准备。

说起AC自动机,就让我想起了数字电路里面的状态图和状态转移。当初一道实验题目就是设计一个电路,当输入的一串0-1信号中出现了给定的模式时输出一个高电平,做法就是根据特定模式编程设计一个状态图,然后根据不同的输入在状态图上转移,在某些状态下输入特定值则输出高电平。

发现AC自动机跟这个有异曲同工之妙。AC自动机则是根据已知的单词得出单词树(即Trie树),然后在单词树上产生失配指针(即得到状态转移的指针),最后将文本作线性扫描得到结果。

当然AC自动机与数电实验还是有一定区别。详细解析见下文:

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

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

3.AC自动机
之前自己写了一个AC自动机的主程序,结果是错的。后来在网上找到一段代码,仔细比较,终于发现了错误,并明白了AC自动机的原理。


对于一个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]是否是单词。

大致总结在这里吧。没有图片,看起来不是很方便。推荐blog:http://www.cppblog.com/mythit/archive/2009/04/21/80633.html。感觉该blog在AC自动机的运行原理上没有解释得特别清楚,于是写了这片随笔,算是互为补充吧。不足之处欢迎大家指出。最后,谢谢“AC自动机算法详解”的作者。如果有兴趣还可以看看《柔性字符串匹配》这本书。

另外,“AC自动机算法详解”这篇blog上面的代码有一处细微的错误,已经改正。

 

ContractedBlock.gif ExpandedBlockStart.gif HDU 2222 Keywords Search
  1 #include <stdio.h>
  2 #include <string.h>
  3 
  4 const int MaxN = 1000010;
  5 
  6 struct Node {
  7     int count;
  8     struct Node *fail, *next[26];
  9 }node[MaxN<<1];
 10 
 11 typedef struct Node *NodePtr;
 12 
 13 struct Trie {
 14     NodePtr root;
 15     int mtp;
 16     void init() {    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 17         root = node; mtp = 1;
 18         memset(root, 0sizeof(Node));
 19     }
 20     void insert(char *s) {
 21         NodePtr p = root;
 22         while(*s) {
 23             if(!p->next[*s-97]) {
 24                 p->next[*s-97= node+mtp++;
 25                 memset(p->next[*s-97], 0sizeof(Node));
 26             }
 27             p = p->next[*s-97]; s++;
 28         }
 29         p->count++;
 30     }
 31     void fail_ptr();    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 32     int text_match(char *text);    //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
 33 }trie;
 34 
 35 NodePtr Q[MaxN];
 36 int qs, qe;
 37 
 38 void Trie::fail_ptr() {
 39     NodePtr p, q;
 40     Q[0= root; qs = 0; qe = 1;
 41     while(qs != qe) {
 42         p = Q[qs++]; if(qs == MaxN) qs = 0;
 43         q = NULL;
 44         for(int i = 0; i < 26++i) if(p->next[i]) {
 45             if(p == root) p->next[i]->fail = root;
 46             else {
 47                 q = p->fail;
 48                 while(q != NULL) {
 49                     if(q->next[i] != NULL) {
 50                         p->next[i]->fail = q->next[i];
 51                         break;
 52                     }
 53                     q = q->fail;
 54                 }
 55                 if(q == NULL)
 56                     p->next[i]->fail = root;
 57             }
 58             Q[qe++= p->next[i]; if(qe == MaxN) qe = 0;
 59         }
 60     }
 61 }
 62 
 63 int Trie::text_match(char *s) {
 64     NodePtr p = root, temp;
 65     int cnt = 0, i;
 66     while(*s) {
 67         i = *s-97;
 68         while(p->next[i] == NULL && p != root)    //最长后缀
 69             p = p->fail;
 70         p = (p->next[i] == NULL)? root : p->next[i];
 71         temp = p;
 72         while(temp != root){        //注意这段代码
 73             cnt += temp->count; 
 74             temp->count = 0
 75             temp = temp->fail; 
 76         }
 77         s++;
 78     }
 79     return cnt;
 80 }
 81 
 82 char word[55], text[MaxN];
 83 int n;
 84 
 85 int main() {
 86     int t;
 87     for(scanf("%d"&t);t--;) {
 88         scanf("%d%*c"&n);
 89         trie.init();
 90         for(int i = 0; i < n; ++i) {
 91             scanf("%s", word);
 92             trie.insert(word);
 93         }
 94         trie.fail_ptr();
 95         scanf("%s", text);
 96         printf("%d\n", trie.text_match(text));
 97     }
 98     return 0;
 99 }
100 

 

转载于:https://www.cnblogs.com/destinydesigner/archive/2009/10/15/1584191.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值