AC自动机

一:概念

首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段文章(长度是m),让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie的基础知识(也有人说需要KMP的知识,我觉得暂且不要理会这个。但是在看这篇文章之前,Trie字典树,你是必须要先搞懂,如果你还不理解Trie,请参考http://blog.csdn.net/laojiu_/article/details/50838421)。

与其他字符匹配不同,KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。匹配时间复杂度是O(N),线性复杂度!


二:算法过程(三步走)

举个例子,假如现在给出5个模式串:say she shr he her 

主串是:yasherhs

现在问你,这5个模式串有几个出现在主串里的?

OK,现在就拿这个例子来完成这个算法的过程。

第一步:构建Trie树,这很简单的了。构建好后,出现下图:



第二步:构建失败指针

构建失败指针是AC自动机的核心所在,玩转了它也就玩转了AC自动机,失败指针就是,当我的主串在trie树中进行匹配的时候,如果当前节点不能再继续进行匹配,那么我们就会走到当前节点的fail节点继续进行匹配。

构造失败指针的过程概括起来就一句话:对于root的儿子节点,fail指针直接指向root,其他的所有节点(用到了BFS和队列),设这个节点上的字母为C,沿着它父亲的失败指针走,直到走到一个节点,它的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母为C的节点。如果一直走到了root都没找到,那就把失败指针指向root。

构建好后,如下图:


针对图中红线的”h,e“这两个节点,我们想起了什么呢?对”her“中的”e“来说,e到root距离的n个字符恰好与”she“中的e向上的n个字符相等

第三步:模式匹配

匹配过程分两种情况:

(1)  当前字符匹配成功,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;

(2)  当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。


注意:主串所有字符在匹配完后都必须要走fail节点来结束自己的旅途,相当于一个回旋,这样做的目的防止包含节点被忽略掉。

见下图,比如:我匹配到了"she",必然会匹配到该字符串的后缀”he",要想在程序中匹配到,则必须节点要走失败指针来结束自己的旅途。



三:完整代码

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. #include<iostream>  
  2. #include<queue>  
  3.   
  4. #define MAX 26//假设只出现26个小写英文字母  
  5. #define ROW 4  
  6. #define COLUMN 10  
  7.   
  8. using namespace std;  
  9.   
  10. char pattern[ROW][COLUMN] = { "nihao","hao","hs","hsr" };  
  11. char *s = "sdmfhsgnshejfgnihaofhsrnihao";  
  12.   
  13. struct Node  
  14. {  
  15.     int index;//存储模式串的下标  
  16.     char x;  
  17.     Node *parent;  
  18.     Node *next[MAX];  
  19.     Node *fail;  
  20.     Node()  
  21.     {  
  22.         index = -1;//pattern数组下标从0开始,-1代表该节点不是单词结尾  
  23.         fail = nullptr;   
  24.         parent = nullptr;  
  25.         for (int i = 0; i < MAX; i++)  
  26.             next[i] = nullptr;  
  27.     }  
  28. };  
  29.   
  30. class ACTree  
  31. {  
  32. public:  
  33.     Node *root;  
  34.     ACTree() { root = new Node; root->fail = root; }  
  35.   
  36.     void Add(const char *ch, int index);              //第一步  
  37.     void NodeToQueue(Node *node, queue<Node*> &q);    //  
  38.     void BuildFailPointer();                          //第二步  
  39.     void ACSearch(const char *s);                     //第三步  
  40. };  
  41.   
  42. int main()   
  43. {  
  44.     ACTree tree;  
  45.   
  46.     for (int i = 0; i < ROW; i++)  
  47.         tree.Add(pattern[i], i);  
  48.   
  49.     tree.BuildFailPointer();  
  50.   
  51.     cout << "待匹配字符串为(依次5个一组的输出):\n";  
  52.     for (int i = 1; i <= strlen(s); i++)  
  53.     {  
  54.         cout << s[i];  
  55.         if (i % 5 == 0)  
  56.             cout << "  ";  
  57.     }  
  58.     cout << endl << endl;  
  59.   
  60.     cout << "匹配结果如下:\n";  
  61.     cout << "位置\t" << "编号\t" << "模式\n";  
  62.   
  63.     tree.ACSearch(s);  
  64.   
  65.     return 0;  
  66. }  
  67.   
  68. void ACTree::Add(const char *ch,int index)  
  69. {  
  70.     int len = strlen(ch);  
  71.     if (len == 0) return;  
  72.   
  73.     Node *p = root;  
  74.   
  75.     for (int i = 0; i < len; i++)  
  76.     {  
  77.         int k = ch[i] - 'a';  
  78.   
  79.         if (p->next[k] == nullptr)  
  80.         {  
  81.             p->next[k] = new Node;  
  82.             p->next[k]->parent = p;  
  83.             p->next[k]->x = ch[i];  
  84.         }  
  85.           
  86.         p = p->next[k];  
  87.     }  
  88.   
  89.     p->index = index;//注意,在此保证输入的模式串不重复,否则index会被覆盖  
  90. }  
  91.   
  92. void ACTree::NodeToQueue(Node *node, queue<Node*> &q)  
  93. {  
  94.     if (node != nullptr)  
  95.     {  
  96.         for (int i = 0; i < MAX; i++)  
  97.         {  
  98.             if (node->next[i])  
  99.                 q.push(node->next[i]);//不知道这是干嘛的??想想BFS层次遍历的那些事  
  100.         }  
  101.     }  
  102. }  
  103.   
  104. void ACTree::BuildFailPointer()  
  105. {  
  106.     queue<Node*> q;  
  107.   
  108.     for (int i = 0; i < MAX; i++)  
  109.     {  
  110.         if (root->next[i])  
  111.         {  
  112.             NodeToQueue(root->next[i], q);//注意,切不可写成q.push(root->next[i]);  
  113.             root->next[i]->fail = root;  
  114.         }  
  115.     }  
  116.   
  117.     Node *parent, *p;  
  118.     char ch;  
  119.     while (!q.empty())  
  120.     {  
  121.         p = q.front();  
  122.         ch = p->x;  
  123.         parent = p->parent;  
  124.         q.pop();  
  125.         NodeToQueue(p, q);  
  126.   
  127.         while (1)  
  128.         {  
  129.             if (parent->fail->next[ch - 'a'] != nullptr)  
  130.             {  
  131.                 p->fail = parent->fail->next[ch - 'a'];  
  132.                 break;  
  133.             }  
  134.             else  
  135.             {  
  136.                 if (parent->fail == root)  
  137.                 {  
  138.                     p->fail = root;  
  139.                     break;  
  140.                 }  
  141.                 else  
  142.                     parent = parent->fail->parent;  
  143.             }  
  144.         }  
  145.     }  
  146. }  
  147.   
  148. void ACTree::ACSearch(const char *s)  
  149. {  
  150.     int len = strlen(s);  
  151.     if (len == 0) return;  
  152.   
  153.     Node *p = root;  
  154.   
  155.     int i = 0;  
  156.     while (i < len)  
  157.     {  
  158.         int k = s[i] - 'a';  
  159.   
  160.         if (p->next[k] != nullptr)  
  161.         {  
  162.             p = p->next[k];  
  163.   
  164.             if (p->index != -1)  
  165.                 cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;  
  166.   
  167.             i++;  
  168.         }  
  169.         else  
  170.         {  
  171.             if (p == root)  
  172.                 i++;  
  173.             else  
  174.             {  
  175.                 p = p->fail;  
  176.                 if (p->index != -1)  
  177.                     cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;  
  178.             }  
  179.         }  
  180.     }  
  181.   
  182.     while (p != root)  
  183.     {  
  184.         p = p->fail;  
  185.         if(p->index!=-1)  
  186.             cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;  
  187.     }  
  188. }  

四:数据测试



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python AC自动机是一个用于字符串匹配的算法,它可以高效地在一段文本中查找多个预定义的模式。它的实现可以使用多种库,其中包括ac自动机python和ahocorasick-python。 ac自动机python是一个对标准的ac自动机算法进行了完善和优化的实现,适用于主流的Python发行版,包括Python2和Python3。它提供了更准确的结果,并且可以通过pip进行安装,具体的安装方法可以参考官方文档或者使用pip install命令进行安装。 ahocorasick-python是另一个实现AC自动机的库,它也可以用于Python2和Python3。你可以通过官方网站或者GitHub源码获取更多关于该库的信息和安装指南。 对于AC自动机的使用,一个常见的例子是在一段包含m个字符的文章中查找n个单词出现的次数。要了解AC自动机,需要有关于模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机的算法包括三个步骤:构造一棵Trie树,构造失败指针和模式匹配过程。在构造好AC自动机后,可以使用它来快速地在文本中查找预定义的模式,并统计它们的出现次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [ahocorasick-python:AC自动机python的实现,并进行了优化。 主要修复了 查询不准确的问题](https://download.csdn.net/download/weixin_42122986/18825869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python实现多模匹配——AC自动机](https://blog.csdn.net/zichen_ziqi/article/details/104246446)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值