AC自动机
- AC自动机介绍
- 代码演示
- indexTree
AC自动机介绍
AC自动机算法是一种基于Trie树和有限状态机的字符串匹配算法。它在查找字符串时,利用额外的失配指针进行回退,转向其他分支,避免重复匹配前缀,从而提高算法效率。当一个字典串集合是已知的,AC自动机算法可以以离线方式先求出并储存自动机,以便日后使用。在这种情况下,算法的时间复杂度为输入字符串长度和匹配数量之和。AC自动机算法的主要优势是高效、快速,能够在大量文本中快速查找匹配项。
AC自动机算法的流程包括以下几个步骤:
1.构建Trie树:将所有字典串集合中的串进行前缀压缩,得到Trie树。
2.构建Fail指针:从根节点开始,按照字典序遍历所有节点,为每个节点设置Fail指针
3.初始化:将初始状态设为根节点。
4.匹配:从左到右扫描输入字符串,根据当前字符找到下一个节点,并更新Fail指针。如果找到匹配的串,则记录下来。
5.回溯:如果匹配失败,则根据Fail指针回溯到下一个节点重新匹配。
通过以上流程,AC自动机算法可以在O(m+n)时间复杂度内完成字符串匹配,其中m是输入字符串的长度,n是字典串集合中的最长串的长度。
用图演示下AC自动机的结构.
红色的指针就是fail 指针.
代码演示
// 前缀树的节点
public static class Node {
// 如果一个node,end为空,不是结尾
// 如果end不为空,表示这个点是某个字符串的结尾,end的值就是这个字符串
public String end;
// 只有在上面的end变量不为空的时候,endUse才有意义
// 表示,这个字符串之前有没有加入过答案
public boolean endUse;
public Node fail;
public Node[] nexts;
public Node() {
endUse = false;
end = null;
fail = null;
nexts = new Node[26];
}
}
public static class ACAutomation {
private Node root;
public ACAutomation() {
root = new Node();
}
public void insert(String s) {
char[] str = s.toCharArray();
Node cur = root;
int index = 0;
for (int i = 0; i < str.length; i++) {
index = str[i] - 'a';
if (cur.nexts[index] == null) {
cur.nexts[index] = new Node();
}
cur = cur.nexts[index];
}
cur.end = s;
}
public void build() {
Queue<Node> queue = new LinkedList<>();
queue.add(root);
Node cur = null;
Node cfail = null;
while (!queue.isEmpty()) {
// 某个父亲,cur
cur = queue.poll();
for (int i = 0; i < 26; i++) { // 所有的路
// cur -> 父亲 i号儿子,必须把i号儿子的fail指针设置好!
if (cur.nexts[i] != null) { // 如果真的有i号儿子
cur.nexts[i].fail = root;
cfail = cur.fail;
while (cfail != null) {
if (cfail.nexts[i] != null) {
cur.nexts[i].fail = cfail.nexts[i];
break;
}
cfail = cfail.fail;
}
queue.add(cur.nexts[i]);
}
}
}
}
// 大文章:content
public List<String> containWords(String content) {
char[] str = content.toCharArray();
Node cur = root;
Node follow = null;
int index = 0;
List<String> ans = new ArrayList<>();
for (int i = 0; i < str.length; i++) {
index = str[i] - 'a'; // 路
// 如果当前字符在这条路上没配出来,就随着fail方向走向下条路径
while (cur.nexts[index] == null && cur != root) {
cur = cur.fail;
}
// 1) 现在来到的路径,是可以继续匹配的
// 2) 现在来到的节点,就是前缀树的根节点
cur = cur.nexts[index] != null ? cur.nexts[index] : root;
follow = cur;
while (follow != root) {
if (follow.endUse) {
break;
}
// 不同的需求,在这一段之间修改
if (follow.end != null) {
ans.add(follow.end);
follow.endUse = true;
}
// 不同的需求,在这一段之间修改
follow = follow.fail;
}
}
return ans;
}
}
indexTree
数据结构算法:indexTree