在计算机科学中,文本匹配是一个常见且重要的问题,尤其在信息检索、文本编辑、网络内容过滤等领域。AC 自动机(Aho - Corasick automaton)作为一种高效的多模匹配算法,能够同时查找多个模式串在文本中的出现位置。而 TrieTree(字典树)作为其核心数据结构之一,其构建方式和性能优化至关重要。本文将探讨基于 STL(Standard Template Library)中的 map、unordered_map 和 skiplist 来实现 TrieTree,并分析其在 AC 自动机中的应用原理、优势及应用场景。
二、实现原理
(一)TrieTree 结构概述
TrieTree 是一种树形数据结构,用于存储一组字符串。每个节点代表一个字符,从根节点到某一节点的路径表示一个字符串前缀。TrieTree 的关键特性在于利用字符串的公共前缀来节省存储空间并加速查找过程。
(二)基于 STL 容器的实现方式
- map
- STL 中的 map 是基于红黑树实现的有序关联容器。在实现 TrieTree 时,每个节点可以是一个 map,其中键为字符,值为指向子节点的指针。这种方式的优点是能够自动按照字符顺序排列子节点,便于进行有序遍历和查找。例如,在构建 TrieTree 时插入单词时,map 会自动将字符按照 ASCII 码顺序存储,方便后续的按序查找操作。
- 当构建 AC 自动机时,利用 map 的有序性可以在填充失败指针时更直观地进行节点间的比较和跳转。比如在寻找某个节点的失败指针时,可以按照字符顺序依次查找其祖先节点的子节点,直到找到匹配的失败节点。
- unordered_map
- unordered_map 是基于哈希表实现的无序关联容器。与 map 相比,它在查找、插入和删除操作上平均具有更快的速度,时间复杂度接近常数级别。在 TrieTree 中使用 unordered_map 作为节点的子节点存储结构,可以显著提高查找效率,尤其是在字符集较大时。
- 在 AC 自动机的构建过程中,unordered_map 能够快速定位到当前节点的子节点,加速失败指针的填充过程。例如,当构建一个包含大量不同字符的模式串集合的 AC 自动机时,unordered_map 能够迅速判断某个字符是否存在于当前节点的子节点中,从而快速确定失败指针的指向。
- skiplist
- Skiplist 是一种基于多层链表的概率型数据结构,它通过在链表的不同层次上建立索引来实现快速查找。虽然 STL 没有直接提供 skiplist,但可以借助第三方库或自行实现。在 TrieTree 中使用 skiplist 可以在一定程度上模拟 TrieTree 的层次结构,提高查找效率。
- 在 AC 自动机中,skiplist 可以用于存储 TrieTree 的节点,通过其多层索引结构快速定位到目标节点。例如,在进行文本匹配时,利用 skiplist 的索引可以快速跳过不匹配的节点,加速匹配过程。同时,在构建失败指针时,skiplist 的层次结构也有助于快速找到合适的失败节点。
(三)AC 自动机构建原理
- 构建 TrieTree
- 首先,将所有模式串插入到 TrieTree 中。对于每个模式串,从根节点开始,依次查找每个字符对应的子节点。如果子节点不存在,则创建新的子节点并将其与当前节点相连。重复此过程直到插入完模式串的所有字符,并将最后一个节点标记为结束节点。
- 填充失败指针
- 失败指针是 AC 自动机的关键组成部分,用于在匹配失败时快速跳转到下一个可能的匹配位置。填充失败指针的过程通常采用广度优先搜索(BFS)算法。
- 从根节点开始,将根节点的失败指针指向自身。然后,依次处理每个节点的子节点。对于每个子节点,从其父节点的失败指针开始,沿着失败指针向上查找,直到找到一个节点,该节点的子节点中包含当前子节点的字符。将当前子节点的失败指针指向找到的节点的对应子节点。如果未找到匹配的子节点,则将失败指针指向根节点。
- 在填充失败指针的过程中,还需要将失败指针节点的结束标记传递给当前节点。如果失败指针节点是某个模式串的结束节点,则当前节点也可能是该模式串的结束节点,这有助于在匹配过程中快速判断是否找到匹配的模式串。
三、应用场景
(一)文本编辑软件中的查找与替换功能
在文本编辑软件中,用户经常需要查找特定的单词或短语,并进行替换操作。AC 自动机可以高效地实现这一功能。将需要查找的所有模式串(如单词列表)构建到 AC 自动机中,然后对文本进行扫描。在扫描过程中,AC 自动机能够快速定位到每个模式串在文本中的出现位置,从而实现高效的查找。一旦找到匹配的模式串,就可以根据用户的需求进行相应的替换操作。
例如,在一个大型文档中查找并替换多个不同形式的公司名称,如“ABC公司”、“ABC有限公司”、“ABC集团”等。将这些公司名称作为模式串构建到 AC 自动机中,然后对文档进行扫描。AC 自动机能够在一次扫描过程中同时查找所有这些模式串,并准确地找到它们在文档中的位置,大大提高了查找与替换的效率。
(二)网络内容过滤系统
网络内容过滤系统用于过滤网络中的不良内容,如垃圾邮件、恶意网站等。AC 自动机可以用于构建高效的过滤规则引擎。将需要过滤的关键字、短语或模式串(如不良词汇、恶意网址特征等)构建到 AC 自动机中,然后对网络流量中的文本内容进行实时扫描。当检测到匹配的模式串时,系统可以立即采取相应的过滤措施,如阻止访问、标记为垃圾邮件等。
例如,在一个邮件过滤系统中,需要过滤包含特定不良词汇的邮件。将这些不良词汇作为模式串构建到 AC 自动机中,当邮件到达时,系统利用 AC 自动机快速扫描邮件内容。如果发现邮件中包含匹配的不良词汇,系统就可以立即将其标记为垃圾邮件并进行相应的处理,从而有效地过滤不良邮件,保护用户的网络安全。
(三)搜索引擎中的关键词匹配
搜索引擎需要从海量的网页数据中快速找到与用户输入的关键词相关的网页。AC 自动机可以用于优化搜索引擎的关键词匹配过程。将用户的搜索关键词构建到 AC 自动机中,然后对网页的标题、摘要等关键信息进行扫描。AC 自动机能够快速定位到包含关键词的网页,并根据匹配程度对网页进行排序,从而为用户提供更准确的搜索结果。
例如,当用户搜索“旅游景点推荐”时,搜索引擎将这个关键词构建到 AC 自动机中。然后对各个网页的关键信息进行扫描,AC 自动机能够快速找到包含“旅游景点推荐”相关词汇的网页,如“十大旅游景点推荐”、“国内旅游景点推荐”等。根据匹配程度和网页的其他相关性因素,搜索引擎对这些网页进行排序,将最符合用户需求的网页展示在搜索结果的前面,提高搜索的准确性和效率。
四、优势
(一)高效性
- 快速查找
- TrieTree 的结构特点使其在查找字符串时具有较高的效率。在基于 STL 容器实现的 TrieTree 中,无论是使用 map、unordered_map 还是 skiplist,都能够快速定位到目标节点。例如,在使用 unordered_map 实现的 TrieTree 中,查找操作的平均时间复杂度接近常数级别,这使得在大规模数据中查找模式串时能够快速得到结果。
- 在 AC 自动机中,这种快速查找能力尤为重要。当对文本进行扫描匹配时,AC 自动机能够迅速判断当前字符是否匹配某个模式串的前缀,并通过失败指针快速跳转到下一个可能的匹配位置,从而实现高效的多模匹配。
- 批量匹配
- AC 自动机能够同时处理多个模式串的匹配问题,这是其相较于单模匹配算法(如 KMP 算法)的主要优势之一。在构建 TrieTree 时,将所有模式串整合到同一个树结构中,通过一次扫描就可以同时查找所有模式串在文本中的出现位置。这大大提高了匹配效率,尤其在需要同时查找大量模式串时,能够显著减少匹配时间。
- 例如,在一个文本编辑软件中,用户可能需要同时查找多个不同形式的专有名词、缩写等。使用 AC 自动机可以将这些模式串一次性构建到 TrieTree 中,然后对文本进行一次扫描,就可以找到所有这些模式串的出现位置,而不需要对每个模式串分别进行扫描匹配,从而提高了整体的匹配效率。
(二)灵活性
- 动态构建与更新
- 基于 STL 容器实现的 TrieTree 具有较好的动态构建和更新能力。在使用 map、unordered_map 或 skiplist 时,可以方便地地插入、删除或修改模式串。例如,在一个实时更新的网络内容过滤系统中,新的不良词汇或网址特征可能不断出现,而旧的规则可能需要调整或删除。使用 STL 容器实现的 TrieTree 可以灵活地对 AC 自动机进行更新,及时添加新的模式串或修改现有模式串的匹配规则,而无需重新构建整个 TrieTree,从而保证了系统的实时性和有效性。
- 多种 STL 容器选择
- STL 提供了多种容器,如 map、unordered_map 和 skiplist 等,每种容器都有其独特的特性和优势。在实现 TrieTree 时,可以根据具体的应用场景和需求选择合适的容器。例如,当模式串的字符集较小且需要有序遍历时,可以选择 map;当字符集较大且对查找效率要求较高时,可以选择 unordered_map;当需要在 TrieTree 中进行快速范围查找或近似匹配时,可以考虑使用 skiplist。这种灵活性使得基于 STL 容器的 TrieTree 能够更好地适应不同的应用需求,发挥出各自的优势。
(三)可扩展性
- 支持多种匹配模式
- AC 自动机不仅可以用于精确匹配模式串,还可以通过一些扩展实现模糊匹配、近似匹配等功能。例如,在搜索引擎中,用户可能输入一些拼写错误或变体形式的关键词,此时可以通过对 AC 自动机进行扩展,使其能够识别这些模糊匹配的情况。在 TrieTree 中,可以在节点上添加一些额外的信息或规则,如编辑距离限制、同义词映射等,从而实现对不同匹配模式的支持。
- 例如,当用户搜索“travele”(拼写错误)时,搜索引擎可以通过扩展的 AC 自动机识别出与“travel”相关的网页,并将其展示给用户。这种可扩展性使得 AC 自动机能够更好地适应用户的不同输入习惯和搜索需求,提供更准确、更全面的搜索结果。
- 与其他数据结构结合
- 基于 STL 容器的 TrieTree 可以方便地与其他数据结构结合使用,进一步扩展其功能和应用场景。例如,可以将 TrieTree 与后缀数组、后缀树等数据结构结合,实现更高效的文本索引和模式匹配功能。在一些复杂的文本处理任务中,如基因序列分析、自然语言处理等,这种结合可以充分发挥各种数据结构的优势,提高处理效率和准确性。
- 例如,在基因序列分析中,需要对大量的基因序列进行模式匹配和相似性分析。可以将基于 STL 容器的 TrieTree 与后缀树结合,利用 TrieTree 快速构建模式串集合,然后通过后缀树对基因序列进行索引和匹配,从而实现高效的基因序列分析和模式识别。
五、实际应用中的优势体现
(一)信息检索系统
在信息检索系统中,如企业内部的知识管理系统、图书馆的电子资源检索系统等,AC 自动机结合基于 STL 容器的 TrieTree 能够快速响应用户的查询请求。当用户输入多个关键词进行检索时,系统可以利用 AC 自动机同时匹配这些关键词在文档中的出现位置,并根据匹配程度对检索结果进行排序。例如,在一个企业的知识管理系统中,员工可能需要检索包含多个技术术语的文档。使用 AC 自动机可以将这些技术术语构建到 TrieTree 中,然后对文档库进行扫描,快速找到包含这些术语的文档,并按照相关性排序展示给员工,提高检索效率和准确性。
(二)自然语言处理
在自然语言处理领域,如文本分类、情感分析、机器翻译等任务中,AC 自动机可以用于特征提取和模式匹配。例如,在文本分类中,可以将不同类别的文本特征词构建到 AC 自动机中,然后对待分类文本进行扫描,快速提取出特征词及其出现位置。这些特征信息可以用于训练分类模型,提高文本分类的准确性和效率。在情感分析中,可以将表示不同情感倾向的词汇构建到 TrieTree 中,通过 AC 自动机匹配文本中的情感词汇,从而判断文本的情感倾向。在机器翻译中,可以利用 AC 自动机匹配源语言中的短语或模式串,并将其映射为目标语言的对应翻译,提高翻译质量和效率。
(三)网络安全防护
在网络安全防护领域,如入侵检测系统、恶意软件检测系统等,AC 自动机结合基于 STL 容器的 TrieTree 可以快速检测网络流量中的恶意模式。将已知的恶意攻击特征、病毒签名等构建到 TrieTree 中,然后对网络数据包进行实时扫描。当检测到匹配的恶意模式时,系统可以立即采取相应的防护措施,如阻断连接、隔离恶意数据包等。例如,在一个入侵检测系统中,需要检测多种网络攻击模式,如端口扫描、DDoS 攻击、漏洞利用等。使用 AC 自动机可以将这些攻击模式的特征串构建到 TrieTree 中,通过对网络流量的实时监测,快速发现潜在的攻击行为,并及时进行告警和防护,保障网络系统的安全稳定运行。
六、高性能AC自动机:基于TrieTree的优化与实现(C/C++代码实现)
#pragma once
#include <math.h>
#include <time.h>
#include <algorithm>
#include <iostream>
using namespace std;
template <class KeyType, class ValueType>
struct element_t {
...
key_type first;
value_type second;
element_t() {}
explicit element_t(key_type _key, const_reference_value_type _value)
: first(_key), second(_value) {}
operator std::pair<key_type, value_type>() {
return std::make_pair(first, second);
}
bool operator==(const element_t& other) { return other.first == first; }
bool operator!=(const element_t& other) { return other.first != first; }
};
template <class KeyType, class ValueType>
struct skipNode {
...
// element
element_type element;
// next nodes
self_pointer_type* next = nullptr;
skipNode() {}
explicit skipNode(KeyType key, const ValueType& value, int size) {
element.first = key;
element.second = value;
next = new self_pointer_type[size];
}
explicit skipNode(element_const_reference_type theElement, int size) {
element = theElement;
next = new self_pointer_type[size];
}
};
template <class KeyType, class ValueType>
class skiplist {
public:
...
using key_type = KeyType;
using const_key_type = const key_type&;
using value_type = ValueType;
using reference_type = value_type&;
using const_reference_value_type = const value_type&;
using node_type = skipNode<KeyType, value_type>;
using node_pointer_type = node_type*;
using node_reference_type = node_type&;
using node_const_reference = const node_reference_type;
using node_pointer_reference_type = node_pointer_type&;
static constexpr key_type TAIL_INFINITY_KEY =
numeric_limits<key_type>::max();
public:
explicit skiplist(float prob = 0.5f,
KeyType tailLargeKey = TAIL_INFINITY_KEY,
int max_level = 10, int number_node = -1) {
srand(time(nullptr));
m_prob = prob;
m_size = 0;
// 计算最大链表层数
if (number_node != -1)
m_maxLevel = MaxLevel(number_node, prob);
else
m_maxLevel = max_level;
m_curMaxLevel = 0;
// 初始化尾节点
m_tailKey = tailLargeKey;
element_type tailPair(m_tailKey, 0);
m_tailNode = new node_type(tailPair, 0);
// 初始化头结点
m_headNode = new node_type(tailPair, m_maxLevel);
for (int i = 0; i < m_maxLevel; i++) {
m_headNode->next[i] = m_tailNode;
}
// 缓存前驱节点
m_forwardNodes = new node_pointer_type[m_maxLevel];
}
~skiplist() {
while (m_headNode != m_tailNode) {
auto x = m_headNode->next[0];
delete m_headNode;
m_headNode = x;
}
delete m_tailNode;
delete[] m_forwardNodes;
}
class iterator {
public:
explicit iterator(node_pointer_type x) : it(x) {}
operator element_type() { return it->element; }
element_type& operator*() { return (it->element); }
element_type* operator->() { return &it->element; }
iterator& operator++() {
it = it->next[0];
return *this;
}
iterator operator++(int) {
iterator old(it);
it = it->next[0];
return old;
}
bool operator==(const iterator& iter) { return iter.it == it; }
bool operator!=(const iterator& iter) { return iter.it != it; }
private:
node_pointer_type it;
};
iterator begin() { return iterator(m_headNode->next[0]); }
iterator end() { return iterator(m_tailNode); }
using const_iterator = iterator;
node_pointer_type insert(const_key_type key,
const_reference_value_type value) {
// 随机生成索引节点层数 1<=level<=m_maxLevel
int level = random_level();
if (level > m_curMaxLevel) {
m_curMaxLevel = level;
}
node_pointer_type pNode = search(key);
if (pNode->element.first == key) {
pNode->element.second = value;
return pNode;
}
// 此时已经保存了合适的前驱节点m_forwardNodes
// 创建一个具有level层的节点
node_pointer_type pNewNode = new node_type(key, value, level);
// 建立索引节点
for (int i = level - 1; i >= 0; --i) {
pNewNode->next[i] = m_forwardNodes[i]->next[i];
m_forwardNodes[i]->next[i] = pNewNode;
}
m_size++;
return pNewNode;
}
node_pointer_type insert(element_const_reference_type element) {
return insert(element.first, element.second);
}
bool erase(const_key_type key) {
// 不符合的key
if (key > m_tailKey) return false;
node_pointer_type pNode = search(key);
// 不存在
if (pNode->element.first != key) return false;
// 更新跳表链表结构
for (int i = m_curMaxLevel - 1; i >= 0; --i) {
// 此处 m_forwardNodes[i]->next[i] 可能不是 pNode
if (m_forwardNodes[i]->next[i] == pNode)
m_forwardNodes[i]->next[i] = pNode->next[i];
}
// 维护当前最大层级数
// 当删除一个具有最大层级的节点时,可能会导致
// m_headNode->next[m_maxLevel-1]=m_tailNode,那么此时需要降低层级
while (m_curMaxLevel - 1 > 0 &&
m_headNode->next[m_curMaxLevel - 1] == m_tailNode) {
m_curMaxLevel--;
}
delete pNode;
m_size--;
return true;
}
...
// 返回一个迭代器
iterator find(const_key_type key) {
auto x = find_node(key);
if (x == nullptr) return end();
return iterator(x);
}
element_pointer_type find_element(const_key_type key) {
node_pointer_type x = find_node(key);
if (!x) return nullptr;
return &x->element;
}
/* 搜索并把遇到的最后一个节点保存下来 */
node_pointer_type search(const_key_type key) {
node_pointer_type forwardNode = m_headNode;
// 外层循环: 不断的指向下一层
for (int i = m_curMaxLevel - 1; i >= 0; --i) {
// 内层循环: 指向当前层链表的下一个节点
while (forwardNode->next[i] != m_tailNode &&
forwardNode->next[i]->element.first < key) {
forwardNode = forwardNode->next[i];
}
// 保存前驱节点指针
m_forwardNodes[i] = forwardNode;
}
// 最终回到第0层
return forwardNode->next[0];
}
int size() { return m_size; }
void output() {
if (m_size <= 0) return;
for (int i = m_curMaxLevel - 1; i >= 0; i--) {
node_pointer_type cur = m_headNode->next[i];
cout << "head" << i << " => ";
while (cur != m_tailNode) {
if (cur == m_headNode->next[i]) {
cout << cur->element.first << ":" << cur->element.second;
} else {
cout << "->" << cur->element.first << ":"
<< cur->element.second;
}
cur = cur->next[i];
}
cout << endl;
}
}
void output_bottom() {
node_pointer_type x = m_headNode->next[0];
while (x != m_tailNode) {
cout << "->" << x->element.first << ":" << x->element.second;
x = x->next[0];
}
cout << endl;
}
...
reference_type operator[](const_key_type key) {
auto x = find_node(key);
if (x == nullptr) return insert(key, value_type())->element.second;
return x->element.second;
}
protected:
// 简单随机生成索引节点层数 1<=level<=m_maxLevel
// int random_level(){
// int level = rand() % m_maxLevel + 1;
// return level;
// }
int random_level() {
int level = 1;
while (((double)(rand()) / RAND_MAX) < (m_prob) && level < m_maxLevel) {
level++;
}
return level;
}
int MaxLevel(int numberOfnode, float prob) {
return (int)(ceil(logf((float)numberOfnode) / logf((float)1 / prob)));
}
private:
// 随机概率
float m_prob;
key_type m_tailKey;
// 最大索引层数
int m_maxLevel;
// 当前某个节点的最大索引层数 1 <= m_curMaxLevel <= m_maxLevel
int m_curMaxLevel;
int m_size;
// 头结点
node_pointer_type m_headNode;
// 尾节点/哨兵节点
node_pointer_type m_tailNode;
// 用于保存前驱节点
node_pointer_type* m_forwardNodes;
};
#pragma once
...
template <class T, int Type>
struct TrieNodeData {
using self_type = TrieNodeData<T, Type>;
...
using iterator = typename node_type::iterator;
using const_iterator = typename node_type::const_iterator;
// 是否是叶子节点, 即是否是一个单词/序列
bool isLeaf;
// 统计重复单词/序列出现的个数
int count;
// 记录一个单词/序列的长度
int length;
// fail指针,用于构建ac自动机
self_type *fail;
// 孩子节点有序trie/无序trie
node_type children;
TrieNodeData(bool _leaf = false, int _count = 0, int _length = 0,
self_type *_fail = nullptr)
: isLeaf(_leaf), count(_count), length(_length), fail(_fail) {}
TrieNodeData(const self_type &other) {
isLeaf = other.isLeaf;
count = other.count;
length = other.length;
fail = nullptr;
}
};
template <class T>
struct isChar {
static constexpr bool value = false;
};
template <>
struct isChar<char> {
static constexpr bool value = true;
};
template <>
struct isChar<wchar_t> {
static constexpr bool value = true;
};
// convert wstring to string
std::ostream &operator<<(std::ostream &out, basic_string<wchar_t> ws) {
std::wstring_convert<std::codecvt_utf8<wchar_t>> convert;
out << convert.to_bytes(ws);
return out;
}
template <class T = char, int Type = 0, T endMark = '\0'>
class TrieTree {
public:
...
using t_iterator = typename sequence_type::iterator;
using ct_iterator = typename sequence_type::const_iterator;
TrieTree() { root = new node_type(); }
~TrieTree() {
clear(root);
if (root) {
delete root;
root = nullptr;
}
}
// trie树迭代器
class iterator {
public:
iterator(typename std::vector<sequence_type>::iterator first) {
it = first;
}
~iterator() {}
bool operator!=(iterator it) { return this->it != it.it; }
iterator operator++(int) {
iterator old(it);
it++;
return old;
}
iterator &operator++() {
it++;
return *this;
}
sequence_type &operator*() { return *it; }
sequence_type *operator->() { return &*it; }
private:
typename std::vector<sequence_type>::iterator it;
};
iterator begin() {
get();
return iterator(__vcLs.begin());
}
iterator end() { return iterator(__vcLs.end()); }
// clone TrieTree
auto clone() {
std::shared_ptr<self_type> self = std::make_shared<self_type>();
for (auto s : get()) {
self->insert(s);
}
return self;
}
// 将一个序列/单词插入到trie中
node_pointer insert(ct_iterator first, ct_iterator last) {
auto x = root;
int length = last - first;
for (; first != last; first++) {
if (*first == endMark) continue;
if (x->children.find(*first) == x->children.end()) {
x->children[*first] = new node_type();
// fail指针,用于构建AC自动机
x->children[*first]->fail = root;
}
x = x->children[*first];
}
x->length = length;
x->isLeaf = true;
x->count++;
return x;
}
node_pointer insert(const_reference_list_type s) {
return insert(s.begin(), s.end());
}
// 查找一个序列/单词是否存在
node_pointer search(ct_iterator first, ct_iterator last) {
if (root == nullptr) return nullptr;
auto x = root;
for (; first != last; first++) {
...
}
return x->isLeaf ? x : nullptr;
}
template <class C>
node_pointer search(const C &c) {
return search(std::begin(c), std::end(c));
}
node_pointer search(const_reference_list_type s) {
return search(s.begin(), s.end());
}
// 前缀匹配
node_pointer prefix_find(const_reference_list_type s) {
if (root == nullptr) return nullptr;
auto x = root;
for (auto it = s.begin(); it != s.end(); it++) {
...
}
return x;
}
// 删除一个序列/单词
bool erase(node_pointer_ref x, ct_iterator first, ct_iterator last) {
if (!x) return false;
if (*first != endMark) {
if (x->children.find(*first) != x->children.end()) {
if (erase(x->children[*first], next(first, 1), last) &&
x->isLeaf == false) {
// 没有孩子节点,则删除
if (!hasChildren(x) && x->count == 0) {
x->children.erase(*first);
delete x;
x = nullptr;
return true;
} else {
x->children.erase(*first);
return false;
}
} else {
// 如果当前节点没有孩子节点,则从哈希表/红黑树中删除多余的
// 不存在的 key-value
if (!hasChildren(x)) {
x->children.erase(*first);
}
}
}
}
// 叶子节点
if (*first == endMark && x->isLeaf) {
x->count--;
// 只有在 count=0 时才真正的删除序列/单词
if (!hasChildren(x) && x->count == 0) {
delete x;
x = nullptr;
return true;
} else {
if (x->count == 0) x->isLeaf = false;
return false;
}
}
return false;
}
bool erase(const_reference_list_type s) {
return erase(root, s.begin(), s.end());
}
// 统计某个序列/单词重复出现的次数
int count(ct_iterator first, ct_iterator last) {
if (!root) return 0;
auto x = root;
for (; first != last; first++) {
if (*first == endMark) continue;
if (x->children.find(*first) != x->children.end()) {
x = x->children[*first];
} else {
return 0;
}
}
// 只有到达叶子节点才可以得到正确的count
if (x->isLeaf) return x->count;
return 0;
}
int count(const_reference_list_type c) { return count(c.begin(), c.end()); }
// 获取当前trie树所有的单词/序列
void get(node_pointer x, sequence_type &v) {
if (!x) return;
for (auto it : x->children) {
...
get(it.second, v);
v.pop_back();
}
}
decltype(auto) get() {
__vcLs.clear();
sequence_type v;
get(root, v);
return __vcLs;
}
// 所有匹配的前缀单词
void __prefix(node_pointer x, sequence_type s,
std::vector<sequence_type> &words) {
if (!x) return;
for (auto it : x->children) {
...
__prefix(it.second, s, words);
s.pop_back();
}
}
/**
* @brief 获取所有的前缀单词
* @note
* @param &prefix_str:
* @retval
*/
auto prefixWords(const_reference_list_type prefix_str) {
auto x = prefix_find(prefix_str);
std::vector<sequence_type> words;
if (!x) return words;
if (x->isLeaf) {
for (int i = 0; i < x->count; i++) words.push_back(prefix_str);
}
__prefix(x, prefix_str, words);
return words;
}
// 清空TrieTree
void clear(node_pointer_ref x) {
if (!x) return;
for (auto &i : x->children) {
clear(i.second);
// 回溯过程中删除节点
delete i.second;
i.second = nullptr;
}
}
void clear() {
clear(root);
root->children.clear();
}
node_pointer_ref get_root() { return root; }
protected:
// 是否有孩子节点
bool hasChildren(node_pointer x) {
for (auto it : x->children)
if (it.second) return true;
return false;
}
private:
node_pointer root;
std::vector<sequence_type> __vcLs;
};
template <class T = char, int Type = 1, T endMark = '\0'>
class AC_automaton {
public:
...
AC_automaton() { root = trie.get_root(); }
~AC_automaton() {}
// 构建Trie树
void buildTrieTree(const std::vector<sequence_type> &vs) {
for (auto x : vs) trie.insert(x);
}
// 构建ac自动机
void buildAC_automaton() {
std::queue<node_pointer> q;
q.push(root);
while (!q.empty()) {
node_pointer x = q.front();
q.pop();
// 当前节点x的子节点
for (auto xc : x->children) {
node_pointer xf = x->fail;
// 沿着树向上,直到根节点的fail=nullptr
while (xf != nullptr) {
...
}
xf = xf->fail;
}
q.push(xc.second);
}
}
}
void start_ac_automaton(const std::string &s) {
node_pointer x = root;
for (int i = 0; i < s.size(); i++) {
char c = s.at(i);
..
node_pointer z = x;
while (z != root) {
// 只有在叶子节点才是一个完整的单词/序列
if (z->isLeaf) {
int pos = i + 1 - z->length;
std::cout << s.substr(pos, z->length) << "\tindex: " << pos
<< "\tlength : " << z->length
<< "\tcount : " << z->count << std::endl;
}
// 跳转到另外的分支输出匹配的字符串
z = z->fail;
}
}
}
private:
TrieTree<T, Type, endMark> trie;
node_pointer root;
};
测试例子:
void test1() {
// ac自动机 多模式串匹配
AC_automaton<char, MODE> aca;
vector<string> vs = {"aaa", "abcab"};
vector<string> ss{"bcd", "ce", "ce", "ababa", "abce"};
aca.buildTrieTree(ss);
aca.buildAC_automaton();
aca.start_ac_automaton("abcexbcdxxxcebcdbceabcece");
}
void test2() {
// 整数序列
TrieTree<int, MODE, 0> trie;
trie.insert({1, 2, 4, 7, 6, 6, 1});
trie.insert({200, 10, 21, 1293, 1, 53, 2});
trie.insert({1, 2, 4, 5, 6, 6, 1});
trie.insert({1, 3, 4, 1, 6, 6, 1});
for (auto i : trie) {
for (auto j : i) cout << j << " ";
cout << endl;
}
cout << trie.count({1, 2, 4, 5, 6, 6, 1}) << endl;
trie.erase({1, 2, 4, 5, 6, 6, 1});
auto y = trie.prefixWords({1, 2, 4});
for (auto yy : y) {
copy(yy.begin(), yy.end(), ostream_iterator<int>(cout, " "));
}
}
If you need the complete source code, please add the WeChat number (c17865354792)
总结
基于 STL map、unordered_map 和 skiplist 的 TrieTree 在 AC 自动机中的应用具有重要的理论和实践意义。通过合理选择和使用 STL 容器,可以高效地构建和操作 TrieTree,从而实现 AC 自动机的快速多模匹配功能。在文本编辑、网络内容过滤、信息检索、自然语言处理、网络安全防护等多个领域,AC 自动机结合基于 STL 容器的 TrieTree 都展现出了显著的优势,能够提高系统的性能、灵活性和可扩展性,为解决复杂的文本匹配问题提供了有力的工具和方法。随着计算机技术的不断发展和应用场景的日益复杂,对高效文本匹配算法和数据结构的需求也将不断增加,AC 自动机及其相关实现技术将继续发挥重要作用,并有望得到进一步的优化和创新。
Welcome to follow WeChat official account【程序猿编码】