Aho-Corasick算法
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Aho-Corasick算法
2018-03-15 10:25:02
在计算机科学中,Aho–Corasick算法是由Alfred V. Aho和Margaret J.Corasick 发明的字符串搜索算法,⽤于在输⼊的⼀串字符串中匹配有限组“字典”中的⼦串。
它与普通字符串匹配的不同点在于同时与所有字典串进⾏匹配。
算法均摊情况下具有近似于线性的时间复杂度,约为字符串的长度加所有匹配的数量。
AC⾃动机主要依靠构造⼀个有限状态机(类似于在⼀个trie树中添加失配指针)来实现。
这些额外的失配指针允许在查找字符串失败时进⾏回退(例如设Trie树的单词cat匹配失败,但是在Trie树中存在另⼀个单词cart,失配指针就会指向前缀ca),转向某前缀的其他分⽀,免于重复匹配前缀,提⾼算法效率。
当⼀个字典串集合是已知的(例如⼀个计算机病毒库), 就可以以离线⽅式先将⾃动机求出并储存以供⽇后使⽤,在这种情况下,算法的时间复杂度为输⼊字符串长度和匹配数量之和。
UNIX系统中的⼀个命令fgrep就是以AC⾃动机算法作为基础实现的。
⼀、⾃动机
⾃动机是计算理论的⼀个概念,其实是⼀张“图”,每个点是⼀个“状态”,⽽边则是状态之间的转移,根据条件能指导从⼀个状态⾛向另⼀个状态。
很多字符串匹配算法都是基于⾃动机模型的,⽐如被⼴泛使⽤的正则表达式。
⼆、AC⾃动机
AC⾃动机可以看成是对KMP算法的推⼴,KMP算法是⼀种单模字符串匹配算法,AC⾃动机是多模字符串匹配算法,可以⼀次对多个pattern进⾏匹配。
AC⾃动机的建⽴流程也很简单,主要分为以下⼏步:
1.建Trie树
2.在Trie树上建⽴失配指针,成为AC⾃动机
3.⾃动机上匹配字符串
下⾯以模式串he/ she/ his /hers为例,待检测⽂本为“ushers”。
1、建⽴Trie树
建⽴Trie树可以说是⾮常模板了。
class TrieNode {
TrieNode[] children;
TrieNode fail;
boolean isWord;
TrieNode() {
children = new TrieNode[26];
fail = null;
isWord = false;
}
}
TrieNode root;
void bulidTrie(String[] patterns) {
root = new TrieNode();
for (String pattern : patterns) {
TrieNode cur = root;
for (int i = 0; i < pattern.length(); i++) {
if (cur.children[pattern.charAt(i) - 'a'] == null)
cur.children[pattern.charAt(i) - 'a'] = new TrieNode();
cur = cur.children[pattern.charAt(i) - 'a'];
}
cur.isWord = true;
}
}
2、建⽴失配指针
AC⾃动机的核⼼就是建⽴失配指针,其思路和KMP算法⾮常类似,在KMP中如果⽂本的text[i...j] 和 pattern[0...j - i]在text[j]出失配,KMP采取的思路是计算pattern[0...j - i - 1]的最长公共前后缀,然后将pattern向后滑动数位,从最长公共前后缀之后继续⽐较,如果依然失配,则重复上述的流程,直到到⾸位,如果依然失配,则text下移。
在AC⾃动机中也是这样,构造失败指针的过程概括起来就⼀句话:设这个节点上的字母为C,沿着他⽗亲的失败指针⾛,直到⾛到⼀个节点,他的⼉⼦中也有字母为C的节点。
然后把当前节点的失败指针指向那个字母也为C的⼉⼦。
如果⼀直⾛到了root都没找到,那就把失败指针指向root。
具体操作起来只需要:先把root加⼊队列(root的失败指针指向⾃⼰或者NULL),这以后我们每处理⼀个点,就把它的所有⼉⼦加⼊队列,队列为空。
void core() {
Queue<TrieNode> queue = new LinkedList<TrieNode>();
queue.add(root);
while (!queue.isEmpty()) {
TrieNode cur = queue.poll();
for (int i = 0; i < 26; i++) {
if (cur.children[i] != null) {
if (cur == root) cur.children[i].fail = root;
else {
TrieNode tmp = cur.fail;
while (tmp != null) {
if (tmp.children[i] != null) {
cur.children[i].fail = tmp.children[i];
break;
}
tmp = tmp.fail;
}
if (tmp == null) cur.children[i].fail = root;
}
queue.add(cur.children[i]);
}
}
}
}
3、在⾃动机上进⾏匹配
int query(String text) {
int res = 0;
TrieNode pre = root;
for (int i = 0; i < text.length(); i++) {
int index = text.charAt(i) - 'a';
while (pre.children[index] == null && pre != root) pre = pre.fail; if (pre == root && pre.children[index] == null) continue;
pre = pre.children[index];
TrieNode tmp = pre;
while (tmp != root && tmp.isWord) {
res++;
tmp.isWord = false;
tmp = tmp.fail;
}
}
return res;
}
完整代码:
import java.util.LinkedList;
import java.util.Queue;
public class AhoCorasick {
class TrieNode {
TrieNode[] children;
TrieNode fail;
boolean isWord;
TrieNode() {
children = new TrieNode[26];
fail = null;
isWord = false;
}
}
TrieNode root;
AhoCorasick() {
root = new TrieNode();
}
void bulidTrie(String[] patterns) {
for (String pattern : patterns) {
TrieNode cur = root;
for (int i = 0; i < pattern.length(); i++) {
if (cur.children[pattern.charAt(i) - 'a'] == null)
cur.children[pattern.charAt(i) - 'a'] = new TrieNode();
cur = cur.children[pattern.charAt(i) - 'a'];
}
cur.isWord = true;
}
}
void core() {
Queue<TrieNode> queue = new LinkedList<TrieNode>();
queue.add(root);
while (!queue.isEmpty()) {
TrieNode cur = queue.poll();
for (int i = 0; i < 26; i++) {
if (cur.children[i] != null) {
if (cur == root) cur.children[i].fail = root;
else {
TrieNode tmp = cur.fail;
while (tmp != null) {
if (tmp.children[i] != null) {
cur.children[i].fail = tmp.children[i];
break;
}
tmp = tmp.fail;
}
if (tmp == null) cur.children[i].fail = root;
}
queue.add(cur.children[i]);
}
}
}
}
int query(String text) {
int res = 0;
TrieNode pre = root;
for (int i = 0; i < text.length(); i++) {
int index = text.charAt(i) - 'a';
while (pre.children[index] == null && pre != root) pre = pre.fail; if (pre == root && pre.children[index] == null) continue;
pre = pre.children[index];
TrieNode tmp = pre;
while (tmp != root && tmp.isWord) {
res++;
tmp.isWord = false;
tmp = tmp.fail;
}
}
return res;
}
public static void main(String[] args) {
AhoCorasick ac = new AhoCorasick();
String[] patterns = new String[]{"he", "she", "his", "hers"}; ac.bulidTrie(patterns);
ac.core();
int ans = ac.query("ushers");
System.out.println(ans);
}
}。