Aho - Corasick string matching algorithm
Aho - Corasick string matching algorithm 俗称:多模式匹配算法,它是对 Knuth - Morris - pratt algorithm (单模式匹配算法) 形成多模式匹配算法的一种改进,如果我们用单模式匹配算法实现多模式匹配算法,假如模式串有 M 个 , 则需要重复调用 M 次单模式匹配算法 ;
举个很简单的例子,假如我现在有一本特殊的字典,字典中的词汇就是所有的模式串,然后给你一篇文章(全英文),让你查一下这篇文章中有多少个词汇在字典中可以查得到;(为了使问题简单化,我们可以忽略单词之间的空格及标点符号)
想一想,如何在最短的时间内结束查找过程?
首先,我们需要对这本字典建立索引,这个索引应该怎么建立呢?当然是按照常规英文字典进行建立(这里就不详细介绍了,默认大家高中都用过英文字典吧,但是为了接近机器思维,要求我们每次只能匹配一个字符),但是当查到不匹配的字符该怎么办啊?假如这个字符就是这个模式串的最后一个字符了,如果匹配对了,该多好啊,难道要回溯到与模式串第二个字符进行匹配的字符吗?多可惜啊!前面难道白白匹配了这么多吗?这是要累死我的节奏啊!我可不想这样,肯定有方法可以减少这些重复匹配,我还是看看其他的模式串的前缀有没有和当前模式串的前缀的一部分相同的(假如当前模式串为:ababc), 果然不出所料,找的了一个模式串(abd), 这次总算没白找,那我就从这个模式串往后找吧! 找出模式串与模式串之间的联系,就是这本字典的特殊之处。
例子举完了,下面就该我们如何用计算机实现了:
我们可以把上面例子中的字典构造成一颗树即“字典树”,其实字典树就是一颗特殊的树。从根到每一片叶子都是一个单词,字典树中有多少片叶子就有多少个单词。
字典树的详细构造过程,这里就不细说了,详情请看我的另一篇文章 Trie implementation
字典树构造完成,接下来就是为字典树构造Fail 指针,用于如果当前字符与当期模式串匹配不成功时,与下一个模式串匹配的位置;
如何找到下一个模式串?
如果当前模式串中的字符与当前字符不匹配,然后找到当前模式串中的字符所在结点的父亲结点(若父亲结点为根结点,则让当前模式串的字符所在结点的Fail 指针指向根结点)的Fail指针所指的结点,看其儿子中有没有和当前字符相等的,如果有,则让当前模式串的字符所在结点的Fail 指针指向其儿子结点,否则再找其Fail 指针所指的位置,直到找到根结点的Fail指针(NULL)为止!(根结点的Fail指针为NULL)
下面给出相应的伪代码:
root -> fail ← NULL
q ← root (q为队列,root入队)
while q ≠ empty do
r ←q (头元素出队)
p ←r
for i ← 0 to 26 do //每个结点的儿子结点都需要找到
if r -> next[i] ≠ NULL do
if r == root do //如果其父亲结点为根结点,其Fail 指针指向根结点
r -> next[i] -> fail = root
q ← r ->next[i]
continue
else do
while r -> fail -> next[i] == NULL and r -> fail ≠ root do //找到父亲结点的 Fail 指针指向结点的儿子结点中的字符是否与当前结点中的字符相同
r = r -> fail
end while
if(p -> next[i] == r -> fail -> next[i]) // 如果相同当前结点指向其儿子结点
p -> next[i] -> fail = r -> fail -> next[i]
else
p -> next[i] -> fail = root //否则指向根结点
end if
end if
q ← p -> next[i] //将当前结点入队
end if
end for
end while
这样就完成了每个结点中的 Fail 指针的指向 ;
下面需要进行的是,自动匹配任务了:
先介绍一下思想,其实很简单,一句话:如果匹配,继续往下找,如果不匹配,从当前结点的 Fail 指针所指的结点的儿子相同时开始往下找;
给出相应的伪代码:
len ← Length[ s ]
p ← root
count ← 0
for i ← 0 to len do
r ←NULL
if root -> next[s[i] - 'a'] ≠ NULL do // 如果能够匹配,r 指向当前字符所在的结点,如果当前字符是一个单词的结尾,r 再指向当前结点的 Fail 指针所指 root = root -> next[s[i] - 'a' ] // 的结点,若 r所指的结点恰好又是另外一个单词的结尾,r 继续进行下去,直到 r 找到根结点为止或找到的结点不是
r ← root // 单词的结尾 ,例如(ababd 与 abd , 若当前字符为 d , 则有两个单词可以查到)
while r -> is_over == true && r != root // 因为可以与当前字符匹配,所以应该接着字符串ababd往下查,而不是字符串 abd ,
count ← count + 1
r -> is_over = false
r = r -> fail
` end while
continue
else
while root -> next[s[i] - 'a'] == NULL and root ≠ p do // 如果不能匹配,转到 Fail 指针指导的结点进行匹配
root = root -> fail
end while
if root -> next[s[i] - 'a'] ≠ NULL //如果找到匹配对象,就从匹配对象开始往下找
root = root -> next[s[i] - 'a']
else //如果找不到匹配对象,则从根结点开始往下找
root = p
end if
r ← root
while r -> is_over == true and r ≠ p do // 如果找到模式串的结尾,计数加1,并且 is_over 赋值 false 避免重复查找
count++
r -> is_over ←false
r = r -> fail //当前指针指向其 Fail所指的结点
end while
end for
return count
以上是对于Aho-Corasick Algorithm 的思想描述,及相关伪代码的实现,最最要的还是要会用这样的思想进行问题的解决,在HDUOJ中有这样一道题,大致描述一下题意: 有N个单词,和一段英文,用相关程序解决这段英文中,有这N个单词中的几个;
解题思路:
第一步肯定是用这N个单词建立字典树
第二步为这个字典树完成 Fail 指针
第三步在字典树中进行查找
下面给出相应的代码: (坑了我一两天才找到其中的一个bug)
#include<iostream>
#include<string.h>
#include<string>
#include<queue>
using namespace std ; #define K 26 struct Node {
Node *fail ;
Node *next[K] ;
bool is_over ;
} ; Node *new_Node() {
Node *root = new Node ;
root -> fail = NULL ;
for(int i = 0 ; i < K ; i++)
root ->next[i] = NULL ;
root -> is_over = false ;
return root ;
} void Construct_Trie( Node *root , char *s ) {
int len = strlen(s) ;
for( int i = 0 ; i < len ; i++ ) {
if( root -> next[s[i] - 'a'] == NULL )
root -> next[s[i] - 'a'] = new_Node() ;
root = root -> next[s[i] -'a'] ;
}
root -> is_over = true ;
} void Construct_Fail( Node *root ) {
root -> fail = NULL ;
queue< Node* > q ;
q.push(root) ;
while(!q.empty()) {
Node *p = q.front() ;
Node *r = p ;
q.pop() ;
for(int i = 0 ; i < K ; i++) {
if(p -> next[i] != NULL ) {
if(p == root) {
p ->next[i] -> fail = root ;
q.push(p ->next[i]) ;
continue ;
}
while(p->fail != root && p ->fail ->next[i] == NULL ) // 找到根结点为止
p = p -> fail ;
if( p -> fail -> next[i] != NULL )
r ->next[i] -> fail = p -> fail -> next[i] ;
else
r -> next[i] -> fail = root ;
q.push(r -> next[i]) ;
}
}
}
} int Aho_Croasick( Node *root , char *s ) {
int len = strlen(s) ;
Node *p = root ;
int count = 0 ;
for(int i = 0 ; i < len ; i++) {
Node *r = NULL ;
if(p ->next[s[i] - 'a'] != NULL) {
p = p -> next[s[i] - 'a'] ;
r = p ;
while(r -> is_over && r != root ) { // 找到根结点为止
count++ ;
r ->is_over = false ;
r = r ->fail ;
}
continue ;
}
else {
while( p -> next[s[i] - 'a'] == NULL && p != root ) // 找到根结点为止
p = p -> fail ;
if( p -> next[s[i] - 'a'] != NULL )
p = p -> next[s[i] - 'a'] ;
else
p = root ;
r = p ;
while( r-> is_over && r != root ) { // 找到根结点为止
count++ ;
r -> is_over = false ;
r = r -> fail ;
}
}
}
return count ;
} int main() {
int n ;
cin >> n ;
while(n--) {
int m ;
cin >> m ;
Node *root = new_Node() ;
while(m--) {
char s[10005];
cin >> s ;
Construct_Trie(root, s);
}
char ss[100005] ;
cin >> ss ;
Construct_Fail(root) ;
cout << Aho_Croasick(root,ss) << endl ;
}
return 0 ;
} // 所有查找过程最多不能超过根结点
整整写了一天啊!有木有……看来我也只适合做程序员了……
Aho - Corasick string matching algorithm的更多相关文章
- Binary String Matching(kmp+str)
Binary String Matching 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 Given two strings A and B, whose alp ...
- String Matching Content Length
hihocoder #1059 :String Matching Content Length 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 We define the ...
- (find) nyoj5-Binary String Matching
5-Binary String Matching 内存限制:64MB 时间限制:3000ms 特判: No通过数:232 提交数:458 难度:3 题目描述: Given two strings A ...
- 【CF954I】Yet Another String Matching Problem(FFT)
[CF954I]Yet Another String Matching Problem(FFT) 题面 给定两个字符串\(S,T\) 求\(S\)所有长度为\(|T|\)的子串与\(T\)的距离 两个 ...
- 【ACM】Binary String Matching
Binary String Matching 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 Given two strings A and B, whose alp ...
- nyoj 5 Binary String Matching(string)
Binary String Matching 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 Given two strings A and B, whose alp ...
- 多模字符串匹配算法-Aho–Corasick
背景 在做实际工作中,最简单也最常用的一种自然语言处理方法就是关键词匹配,例如我们要对n条文本进行过滤,那本身是一个过滤词表的,通常进行过滤的代码如下 for (String document : d ...
- string matching(拓展KMP)
Problem Description String matching is a common type of problem in computer science. One string matc ...
- Binary String Matching
问题 B: Binary String Matching 时间限制: 3 Sec 内存限制: 128 MB提交: 4 解决: 2[提交][状态][讨论版] 题目描述 Given two strin ...
随机推荐
- n!的近似值 (stirling approximation)与 大O记法(big -O- notation)
參考wiki: 1.n!的近似值 (stirling approximation) (中文) http://zh.wikipedia.org/wiki/%E6%96%AF%E7%89%B9%E9%9D ...
- Qemu之Network Device全虚拟方案二:虚拟网卡的创建
上文针对Qemu在前端网络流路径的建立方面做了具体的描写叙述.数据包从Host的物理网卡经过Host Linux内核中的Bridge, 经过Tap设备到达了Qemu的用户态空间.而Qemu是怎样把数据 ...
- jQuery模拟原生态App上拉刷新下拉加载
jQuery模拟原生态App上拉刷新下拉加载效果代码,鼠标上拉时会显示loading字样,并且会模拟加载一条静态数据,支持触屏设备使用. <!doctype html> <html ...
- 兼容 CommonJS 和 CommonJS-like规范(1~38)
CommonJS是服务器端模块的规范,Node.js采用了这个规范. 根据CommonJS规范,一个单独的文件就是一个模块.每一个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他 ...
- send js object to webapi or mvc
[HttpPost] public HttpResponseMessage AddInfo(UserInfoEntity userInfo) { return Request.CreateRespon ...
- 初学springMVC搭建框架过程及碰到的问题
刚刚开始学spring框架,因为接了一个网站的项目,想用spring+springMVC+hibernate整合来实现它,现在写下搭建框架的过程及碰到的问题.希望给自己看到也能让大家看到不要踏坑. 一 ...
- TSQL 根据经纬度计算两点间的距离;返回米(m)
-- ============================================= -- Author:Forrest -- Create date: 2013-07-16 -- Des ...
- c++多线程同步使用的对象
线程的同步 Critical section(临界区)用来实现“排他性占有”.适用范围是单一进程的各线程之间.它是: · 一个局部性对象,不是一个核心对象. · 快速而 ...
- flash Builder JSON使用实例
flash Builder JSON 使用说明(转载houdinime) XML虽然强大但有人觉得xml不够简洁,编码和解码也有一定难度,于是21世纪初有人发明了JSON编码,相比xml内容少并且容易 ...
- [转载]node.js express 4.x 安装指南,没有自动配置环境变量的问题
前几天express 推出了4.0,得知这个消息,自己尝试了一下,突然发现用以前的文档上的操作出现了各种问题.结果只能去看文档,现在在这个给大家分享下4.0版本的安装. 先说下如果需要用express ...