提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
AC自动机的构建,分析优化,时间复杂度分析。
提示:以下是本篇文章正文内容,下面案例可供参考
一、AC自动机是什么?
AC自动机就是一个单词查找器,能够高效的查找单词是否存在在字典树里面。
二、自动机的构建。
字典树的构建过程是这样的,当要插入许多单词的时候,我们要从前往后遍历整个字符串,当我们发现当前要插入的字符其节点再先前已经建成,我们直接去考虑下一个字符即可,当我们发现当前要插入的字符没有再其前一个字符所形成的树下没有自己的节点,我们就要创建一个新节点来表示这个字符,接下往下遍历其他的字符。然后重复上述操作。
代码如下(示例):
struct Node {
Node() : flag(false), fail(nullptr) {
for(int i = 0; i < BASE; i++) next[i] = nullptr;
return ;
}
string *s;
bool flag;
Node *next[BASE];
Node *fail;
};
三、自动机朴素实现方法以及优化版本
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <algorithm>
#include <string>
#include <map>
#include <set>
#include <vector>
#include <unordered_set>
using namespace std;
#define BASE 26
struct Node {
Node() : flag(false), fail(nullptr) {
for(int i = 0; i < BASE; i++) next[i] = nullptr;
return ;
}
string *s;
bool flag;
Node *next[BASE];
Node *fail;
};
struct Trie {
public:
Trie() = default;
void insert(string s) {
Node *p = &root;
for(auto x : s) {
int ind = x - 'a';
if(p->next[ind] == nullptr) p->next[ind] = new Node();
p = p->next[ind];
}
if(p->flag == false) {
p->flag = true;
p->s = new string(s);
}
return ;
}
void build_ac_1() {
queue<Node *> q;
// 根节点下面的节点, fail指针, 一定指向根节点
for(int i = 0; i < BASE; i++) {
if(root.next[i] == nullptr) {
continue;
}
root.next[i]->fail = &root;
q.push(root.next[i]);
}
while(!q.empty()) {
Node *now = q.front(), *p;
q.pop();
for(int i = 0; i < BASE; i++) {
if(now->next[i] == nullptr) continue;
p = now->fail; // now -> p -> p'
// 当父节点的fail指针下面也没有i, 那么就不停的向上找
while(p && p->next[i] == nullptr) {
p = p->fail; // p -> p'
}
if(p) p = p->next[i];
else p = &root;
now->next[i]->fail = p;
q.push(now->next[i]);
}
}
return ;
}
// 优化版本建树
void build_ac() {
queue<Node *> q;
// 根节点下面的节点, fail指针, 一定指向根节点
for(int i = 0; i < BASE; i++) {
if(root.next[i] == nullptr) {
root.next[i] = &root;
continue;
}
root.next[i]->fail = &root;
q.push(root.next[i]);
}
while(!q.empty()) {
Node *now = q.front(), *p;
q.pop();
for(int i = 0; i < BASE; i++) {
if(now->next[i] == nullptr) {
// 利用上空节点的作用, 即使为空, 也要利用上
now->next[i] = now->fail->next[i];
continue;
}
now->next[i]->fail = now->fail->next[i];
q.push(now->next[i]);
}
}
return ;
}
//优化后版本匹配
unordered_set<string> match(string &s) {
unordered_set<string> ret;
Node *p = &root, *k;
int cnt = 0;
for(auto x : s) {
int ind = x - 'a';
// 不在有next数组是空了, 我们直接指过去
p = p->next[ind];
cnt++;
k = p;
while(k) {
if(k->flag) ret.insert(*(k->s));
k = k->fail;
}
}
cout << "total operator : " << cnt << endl;
return ret;
}
unordered_set<string> match_2(string &s) {
unordered_set<string> ret;
Node *p = &root, *k;
for(auto x : s) {
int ind = x - 'a';
while(p && p->next[ind] == nullptr) p = p->fail;
if(p) p = p->next[ind];
else p = &root;
k = p;
while(k) {
if(k->flag) ret.insert(*(k->s));
k = k->fail;
}
}
return ret;
}
unordered_set<string> match_1(string &s) {
unordered_set<string> ret;
for(int i = 0; i < s.size(); i++) {
Node *p = &root;
for(int j = i; j < s.size(); j++) {
int ind = s[j] - 'a';
if(p->next[ind] == nullptr) break;
p = p->next[ind];
if(p->flag) ret.insert(*(p->s));
}
}
return ret;
}
private:
Node root;
};
int main() {
int n;
cin >> n;
Trie tree;
string s;
for(int i = 0; i < n; i++) {
cin >> s;
tree.insert(s);
}
tree.build_ac();
cin >> s;
auto ans = tree.match(s);
for(auto x : ans) cout << x << endl;
cout << "find : " << ans.size() << " item(s) " << endl;
return 0;
}
#复杂度分析
**对于Trie的匹配来说时间复杂性为:O(max(L(Pi))L(T))其中L串的长度函数,P是模式串,T是目标串。
对于 AC自动机来说时间复杂性为:O(L(T)+max(L(Pi))+m)气质m是模式串的数量。
对于 Trie 图 来说时间复杂性为:O(L(T))在此的时间复杂性都是指匹配的复杂度。
对于构造的代价是 O(sum(L(Pi)))其中sum是求和函数。**
总结
个人习惯把1设为trie的根trie的存储依题而定,或许可以26个指针,有时卡空间的话要用模拟链表如果多组数据要记得清空tot,trie[][],标记数组,最好不要memset建fail。
创作不易 记得三连哦