Wu-Manber 经典多模式匹配算法

 

多模式匹配的用法,多了去了!DB中对selected patterns进行数挖;安全中对suspicious keyword进行匹配;各种日期形式2009-5-202009520May,20的搜索;DNA配对;各种replace功能;等等,太口水了枚举这个。

       Wu-Manber基于BM算法思想,如果您佬BM还没OK,请参照我的BM日志搞搞清楚先。

       提到Wu-Manber,其实就是SHIFTHASHPREFIX三张表,预处理patterns先把这三张表填好,搜的过程忒简单。

 

1.  拿表说事儿:表干嘛用的

先有个大概印象:SHIFT[]就一跳转表,一张记录向右滑动距离的表。当SHIFT[i]=0时,说明那么多patterns肯定有匹配上的,这时HASH[]PREFIX[]就要站出来指明谁暂时匹配上了,并对它们通通匹配一番。

 

2.  预处理这一堆patterns:填上三表

这堆patterns长度不一,m=最小长度,该算法也就只检测每patm个字符了(那剩余的怎么办呢?o还没想明白)。Patterns长度基本相当最好,假如有一pat掉链子,特短(长度为2),那么每次滑动距离最多也就才2,还滑个什么劲儿呀。所以各pat长度要统一要和谐。

我们把text切成长度为B的块(一般23),B怎么算呢?假如有kpat,那么各pat总长M=k*m|∑|c,那么B=logc(2M)。既然这样,那SHIFT滑动距离就不由末尾单个字符而由末尾B个字符而定了。如果这B个字符跟所有pat都没匹配上,那么很自然小指针可以后滑m-B+1。为什么是m-B+1呢?

 

 

如上图示,虽然XAB不出现在pat末,但它的后缀ABpat前缀,如果你索性移m=5,那么就错过一个匹配了。其实如果B块不出现在pat末,那么至多它的B-1后缀可能出现在pat前缀,因此,安全的移动距离为m-(B-1)=m-B+1。这其实是相对保守的策略,最后我们会提出改进算法。

 

2.1  SHIFT[]

SHIFT[]表大小为|∑| B,因为要组建长度为Bstr,其中每个字符均有|∑|种选择。通过hash function,每个str计算得到一个值index而住进SHIFT。我们现在扫描textX=x1…xB, hashFunc(X)=h,那应该滑动多少呢?

如果X跟各pat都没匹配上,那么SHIFT[h]=m-B+1。如果X跟其中某些pat匹配上,那么SHIFT[h]=m-q,其中qX出现在最右的pat中(假如patj)的xB位置,显然它是最小滑动距离。其实,不用等到实际考查text时再计算SHIFT,我们预处理各pat就可以把SHIFT表填上。对于每个pat,计算其每个长度为Bsubpat(aj-B+1…aj)的值,SHIFT[hashFun(subpat)]=min{m-B+1,m-j}

哈哈,于是乎SHIFT[]里记录了text能安全滑过的最大距离,滑的越大当然越快,但是滑的小一点倒也没错。根据这一点,我们可以压缩SHIFT表,把不同的subpat压缩进一个entry,只要值留最小的那个就OK了。(在agrep这个Wu-Manber算法的应用中,B=2时用的原始SHIFT表,B=3时用的压缩SHIFT表)。

 

2.2  HASH[]

只要SHIFT[]>0,那尽管滑text就好了(100pat的应用中,5%遇到01000个时候27%10000个时候53%)。如果=0,总不至于去跟所有pat一一比对看谁暂时匹配上了吧?o们文明人要用更懒更聪明的方式,去快速锁定那些暂时匹配上的pat们。怎么做呢?HASH

HASH[i]存放一个指向链表的指针,链表存着这样的pat(末B位通过hash function计算是i)。HASH[]表大小同SHIFT,但相对就稀疏多了,人那儿存着所有可能的组合的SHIFT值。哎,牺牲空间换时间,时空一向两难全。

记现正扫描的text的末B位的哈希值为h。同时引入PAT_POINT(指向实际patterns存储位置的指针链表),该链表按patternsB位的哈希值排序,那么HASH[h]的值是pp指向PAT_POINT中哈希值为h的第一个结点处),此时相当于找到第一个跟textB位匹配上的pat,那么进行匹配,如果匹配不上就继续p++,一直到HASH[h+1]指向的那处地址截止。以上是SHIFT[h]=0的情况。对于SHIFT[h]≠0,那么HASH[h]=HASH[h+1],因为没哪个pat的末B位能匹配上,自然这两个HASH值应该相等(开始就是结束)。这样,就填好整张HASH[]表了。

 

2.3  PREFIX[]

       如果只有HASH[]表,就囧大了。例如自然语言text中以ingion结尾的单词非常多,pat中出现ing/ion结尾的也非常多,如果按HASH[]的方法,那就得HASH[h]~HASH[h+1]pat一个个匹配。能不能更快些呢?引入PREFIX[]

       对每一个pat,除了记录其末B位字符的哈希值(PAT_POINT),我们还要记录其首B’位字符的哈希值(PREFIX),一般取B’=2。这是一种有效的过滤手段,因为既末B位相同前B’位也相同的pat很少,这样就没那么多pat需要去匹配了(不像上面那种要一一匹对)。但是也需要权衡啦,因为你计算PREFIX哈希值需要时间,存储它需要空间,换回一一匹配的时间,不知划不划算。

 

3.  匹配过程

Step1. 现正扫描的text的末Btm-B+1…tm通过hash function计算其哈希值h

Step2. 查表,SHIFT[h]>0text小指针后滑SHIFT[h]位,执行1SHIFT[h]=0,执行3

Step3. 计算此mtext的前缀的哈希值,记为text_prefix

Step4. 对于每个pHASH[h]p)看是否PREFIX[p]=text_prefix。如果相等,方才让真正的pat(即PAT_POINT[p])去和text匹配。

 

计算SHIFT[],HASH[],PREFIX[];

//开始匹配

while(text

{

         hashVal=hashBlock(text);//计算当前块的哈希值

         //查找块的坏字符移动表(SHIFT)得到下一个匹配开始位置

         shift_distance=SHIFT[hashval]; //

         if(shift_distance==0) //当前块出现在某pat

         {

                   shift_distance=1; //

                   p=HASH[hashval];

                   //得到可能与当前块匹配的所有pat的集合的开始位置

                   while(p)

                            检验子集中的pat是否匹配;

         }

         text+=shift_distance; // 选择下一个可能的匹配入口

}

 

这里用C给出main(),源码(glimpse代码的一部分)可以从FTP下载:cs.arizona.edu

 

 

 

复杂度OBN/M),BM含义同前,Ntext字符数。Patterns很短或很少的时候,Wu-Manber不是很牛叉。而成千上万的patterns一起匹配过去,Wu-Manber牛气冲天了。

 

3.  Wu-Manber总结与改进

       一言以闭之:WMBM处理多模式的派生形式。用的BM算法框架,用块字符来计算的坏字符移动距离(SHIFT[]);在进行匹配的时候,用的HASH[]选择patterns中的一个子集与当前文本进行匹配,减少无谓的匹配操作。WM不会随着patterns的增加而成比例增长,它远少于使用每一个patBM算法对文本进行匹配的时间总和。

 

改进1

       但是,上文提到过安全的移动距离最大是m-B+1,这是相对保守的策略。其实像图1那种情况的出现次数相对于坏字符情况的出现次数,那真是小乌见大乌了。所以,一次只滑m-B+1多慢呀,能滑m才够快够刺激。呵呵,怎么办呢?想想BM中怎么做地。我们将SHIFT作为坏字符转移表,这样算:

 

 

 

这样,坏字符转移函数的值域0<=y<=m,而不是原来的0<=y<=m-B+1了。下表是SHIFT表的两个版本的实现。SHIFT表空间不变,时间上多了个for循环,总时间是O(B*|∑|+M)|∑|是块集中块的个数。

 

原始实现:

 

m-B+1填写SHIFT;

for each pat

{

         for pat中每一个块

                   计算SHIFT[Bc];

}

 

 

 

改进的实现:

 

m填写SHIFT;

for(i=1;i

{

         对所有Bc[suffix(Bc,i)=prefix(pat,i)]

                   SHIFT[Bc]=m-i;

}

for each pat

{

         for pat中每一个块

                   计算SHIFT[Bc];

}

 

改进2

       WM算法一旦找准匹配入口点,就开始进行逐个的比较,能不能用好后缀呢?呵呵,可以。引入GBSShift[],该表记录了每pat的末B位在所有patterns中的所有非后缀出现位置与相应pat_end的距离的最小值。这样就可以快速确定滑动距离。

 

 

 

       GBSShift[]SHIFT[]大小相同,|∑|GBSShift[]SHIFT[]表计算近似,不需额外计算工作,只需复制计算出的SHIFT[]表计算结果,所以它所需的额外时间是O(|∑|)。将WM伪码中的//改为shift_distance=GBSShift[hashval]即可。

       结合改进12,改进算法在处理大规模数据时比WM算法所用时间养活了8~15%

 

盘点一下:

AC 算法被用于fgrep1.0(在UNIX中通过-f使用)

BM_AC算法在gre中应用,并被fgrep2.0收用。

BMH_AC算法

Wu-Manber算法在agrep中应用,并被glimpse收用。

 

学习自:“A Fast Algorithm For Multi-Pattern Searching”和“一种改进的Wu-Manber多关键词匹配算法”

你可能感兴趣的:(Algorithms,Web)