多模式匹配的用法,多了去了!DB中对selected patterns进行数挖;安全中对suspicious keyword进行匹配;各种日期形式2009-5-20,2009年5月20日,May,20的搜索;DNA配对;各种replace功能;等等,太口水了枚举这个。
Wu-Manber基于BM算法思想,如果您佬BM还没OK,请参照我的BM日志搞搞清楚先。
提到Wu-Manber,其实就是SHIFT、HASH、PREFIX三张表,预处理patterns先把这三张表填好,搜的过程忒简单。
1. 拿表说事儿:表干嘛用的 |
先有个大概印象:SHIFT[]就一跳转表,一张记录向右滑动距离的表。当SHIFT[i]=0时,说明那么多patterns肯定有匹配上的,这时HASH[]和PREFIX[]就要站出来指明谁暂时匹配上了,并对它们通通匹配一番。
2. 预处理这一堆patterns:填上三表 |
这堆patterns长度不一,m=最小长度,该算法也就只检测每pat前m个字符了(那剩余的怎么办呢?o还没想明白)。Patterns长度基本相当最好,假如有一pat掉链子,特短(长度为2),那么每次滑动距离最多也就才2,还滑个什么劲儿呀。所以各pat长度要统一要和谐。
我们把text切成长度为B的块(一般2或3),B怎么算呢?假如有k个pat,那么各pat总长M=k*m,|∑|为c,那么B=logc(2M)。既然这样,那SHIFT滑动距离就不由末尾单个字符而由末尾B个字符而定了。如果这B个字符跟所有pat都没匹配上,那么很自然小指针可以后滑m-B+1。为什么是m-B+1呢?
如上图示,虽然XAB不出现在pat末,但它的后缀AB是pat前缀,如果你索性移m=5,那么就错过一个匹配了。其实如果B块不出现在pat末,那么至多它的B-1后缀可能出现在pat前缀,因此,安全的移动距离为m-(B-1)=m-B+1。这其实是相对保守的策略,最后我们会提出改进算法。
2.1 SHIFT[] |
开SHIFT[]表大小为|∑| B,因为要组建长度为B的str,其中每个字符均有|∑|种选择。通过hash function,每个str计算得到一个值index而住进SHIFT。我们现在扫描text的X=x1…xB, hashFunc(X)=h,那应该滑动多少呢?
如果X跟各pat都没匹配上,那么SHIFT[h]=m-B+1。如果X跟其中某些pat匹配上,那么SHIFT[h]=m-q,其中q是X出现在最右的pat中(假如patj)的xB位置,显然它是最小滑动距离。其实,不用等到实际考查text时再计算SHIFT,我们预处理各pat就可以把SHIFT表填上。对于每个pat,计算其每个长度为B的subpat(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就好了(100个pat的应用中,5%遇到0,1000个时候27%,10000个时候53%)。如果=0,总不至于去跟所有pat一一比对看谁暂时匹配上了吧?o们文明人要用更懒更聪明的方式,去快速锁定那些暂时匹配上的pat们。怎么做呢?HASH!
HASH[i]存放一个指向链表的指针,链表存着这样的pat(末B位通过hash function计算是i)。HASH[]表大小同SHIFT,但相对就稀疏多了,人那儿存着所有可能的组合的SHIFT值。哎,牺牲空间换时间,时空一向两难全。
记现正扫描的text的末B位的哈希值为h。同时引入PAT_POINT(指向实际patterns存储位置的指针链表),该链表按patterns末B位的哈希值排序,那么HASH[h]的值是p(p指向PAT_POINT中哈希值为h的第一个结点处),此时相当于找到第一个跟text末B位匹配上的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中以ing,ion结尾的单词非常多,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的末B位tm-B+1…tm通过hash function计算其哈希值h。
Step2. 查表,SHIFT[h]>0,text小指针后滑SHIFT[h]位,执行1;SHIFT[h]=0,执行3;
Step3. 计算此m位text的前缀的哈希值,记为text_prefix;
Step4. 对于每个p(HASH[h]≦p
计算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
复杂度O(BN/M),B、M含义同前,N是text字符数。Patterns很短或很少的时候,Wu-Manber不是很牛叉。而成千上万的patterns一起匹配过去,Wu-Manber牛气冲天了。
3. Wu-Manber总结与改进 |
一言以闭之:WM是BM处理多模式的派生形式。用的BM算法框架,用块字符来计算的坏字符移动距离(SHIFT[]);在进行匹配的时候,用的HASH[]选择patterns中的一个子集与当前文本进行匹配,减少无谓的匹配操作。WM不会随着patterns的增加而成比例增长,它远少于使用每一个pat和BM算法对文本进行匹配的时间总和。
改进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]即可。
结合改进1、2,改进算法在处理大规模数据时比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多关键词匹配算法”