多模式匹配算法

来源:互联网 发布:知乎 马鹿应援会 编辑:程序博客网 时间:2024/05/01 23:47

1. 问题原型:

         给定一篇网页,其中有很多敏感词汇或者无效的词,需要找到一种算法,找到这些敏感词。

2. 如何求解呢? 
   2.1 第一个简单的思路是: 
          step1: for i = 0 to in #text    
          step2:       foreach pattern p  
          step3:           比较 text[i+j] and p[0+j]  where j = 0 to #p  

     这个思路最大问题是,逐个比较每个文本和每个模式,算法的复杂度是O(m*n*k),m是文本长度,n是pattern个数,k是所有pattern的总长度。my god!可想而知,这样的算法是不可容忍的。那么有哪些改进方法呢:

     首先, hash_map 可以减少大量的比较。

     其次, 对一个模式的比较,有很多算法如: KMP和Boyer-Moore。这两个改进的算法思想是:比较过的文本就不需要在比较了,直接shift 模式而不需要回溯Text,这两个算法可以看这里。以后有机会详细讨论这些。多模式匹配算法也是可以有类似的方法。 
    我们知道,单模式匹配算法的复杂度可以达到O(m+n),所以最基本的改进是: 
    2.2.  naive muti-pattern match 
           step1: foreach pattern p 
           step2:        执行 O(m+#p)的单模式匹配算法  
          算法复杂度是: O(m + #P1 + m + #P2 + …… + m + #pn) = O( n*m + k)

    好慢!如果模式的个数有几百万个,速度是不可忍受

3. 有哪些算法解决多模式匹配问题: 
    AC (Aho-Corasick algorithm
    ACBM(CW)[ A String Matching Algorithm Fast on the Average ] 
    WM [Wu and Manber] 
    ACQS 
    DAWG(ACRF) 
    MultiBDM

3.1 Aho-Corsick Algorithm: 
AC 算法的核心思想是构造词典的自动机(可以使用trie树来实现), 其算法复杂度是O(m+k+z), m是文本长度,k是所有pattern长度之和,z是字符串中出现pattern的个数。

举个例子来说:Pattern P = {he; she; his; hers} ,AC算法将建立如下图的字典树(或者成为keyword tree) 

trie

其中node中有数字的表示在词典(pattern)中,每一条边表示一个字符,同一个节点指出的不同边拥有不同的字符标签。朴素字典树的构建相当简单,   只需要一个pattern一个pattern的插入就可以了(当然其他改进的算法和改进的数据结构,如double-trie)

Construction: 
1. foreach pattern in P{p1,p2,…,pn} 
2. start at the Root 
    2.1 如果从根节点出发的路径在Pi结束之前结束,创建为Pi中每一个没有结束的字符创建节点。 否则继续搜索 
    2.2 创建Pi的终止节点,设置唯一标识 
一旦字典数构建结束,查找过程跟我们平时用查字典的过程是一样的,从根节点一步步向下查,直到找到该词或者没有之结点为止。但是,从以上过程我们发现其复杂度是O(m×k),这显然不是我们想要的结果。okay,那我们该建立一个自动机(automaton):

自动机由状态(states)和行动(action)组成,在本例中: 
State:   字典树的
节点,初始状态为0 
Action:  action有3个函数决定: 
           1.
goto函数 g(q,a), 它表示,如果当前状态是q,输入输入字符是a,那么下一个状态是g(q,a) 

///q-----a-------->r

///状态q表示下图的一个节点,输入字符a 表示一个边,下一个状态r,即节点r为g(q,a)
               a)如果边edge(q, r) 标记为a, 那么 g(q,a) = r 
               b) 如果a不输入0状态的出边 g(0,a)  = 0 , g(q,a) =  $(空) 
           2.
failure函数 f(q), 对q不为0的状态,failure函数表示在状态q失配 
               f(q)是状态机中的一个节点,标记为pattern中某个pattern恰当的最长后缀(如下图5的he) 
           3. out函数 out(q), 状态q,识别出某个pattern

无标题虚线就是表示failure函数的action,比如从5到2的action。如果字符串为sher,搜索到s—h—e,下一个串是r,failure函数可以使得字典树的搜索从5,跳到2, ({he} 是{ she} 的后缀),然后迅速的找到her。 可以看到整个过程中,匹配过程直接按照自动机的顺序匹配,根本不需要回溯字符串(比如sher的例子,一旦she找到,直接从e的下一个字符r开始匹配,而不是从s的下一个字符h开始)。算法如下:

step1 q = 0 
step2 for i = 0 to m do 
step3      while (g(q, T[i])) == $ do 
step4            q = f(q) 
step5       q = g(q, T[i]) 
step6       if out(q) != $ then output(q)

好简洁,这个搜索过复杂度是O(m+z)z是pattern在字符串中出现的次数!证明从略^_^

问题是,这个AC自动机如何构建?

1. 第一步:构建字典数,和root 
2. 第二步:构建failure函数

第一步: 
        1.1 构建字典数, foreach pattern pi in P,设置out(v)= pi,如果v节点标记为pi 
        1.2 构建root, g(0,a) = 0, 如果a不是root的输出边

结果如图:

无标题 

第二步: 
第二步基于一个简单的事实,如果 g(q,a) = u (q---a--->u), 并且{f(q),f(f(q)), f(f(f(q)))…,root}都已经计算出来,那么f(u)为 
第一个不为空的g(fi(q),a),说起来好绕口啊,还是直接看算法吧:

1.  Q = emptyQueue 
2.  foreach a in alphabet   
3       if q(0,a) == q != 0 then 
4              f(q):=0; Q.enqueue(q)  
5   while no Q.empty() do 
6       r = Q.dequeue() 
7       foreach a in alphabet 
8           if g(r,a) == u != null then 
9               Q.enqueue(u) 
10              v = f(r) 
11              while g(v,a) == null do v = f(v) 
12              f(u) = g(v,a) 
13              out(u) = out(u) union out(f(u))

大功告成!注意,AC算法对多模式匹配的一些变种问题也可以很好的处理(e.g  pattern中含有通配符,甚至是正则表达式),代码我就不贴了,download here! ac.h, ac.c

参考: 
1.  http://www.egeen.ee/u/vilo/edu/2005-06/Text_Algorithms/index.cgi?f=L2_Multiple_String 
2.  http://www.itl.nist.gov/div897/sqg/dads/HTML/boyermoore.html

下一篇多模式匹配的ACBM算法,其中还涉及单个模式匹配(或者称为Exact search)的Boyer-Moore技术

0 0
原创粉丝点击