提到多模式匹配算法,就得说一下Wu-Manber算法,其在多模式匹配领域相较于Aho-Corasick算法,就好象在单模式匹配算法中BM算法相较于KMP算法一样,在绝大多数场合,Wu-Manber算法的匹配效率要好于Aho-Corasick算法。这个算法是由吴升(台湾)和他的导师Udi Manber在九十年代提出。当然,要想充分理解WM算法如何加快多模式匹配的效率,还需要对BM算法的深刻了解,可以参考我的另一篇文章《BM算法详解》。

在BM算法中引入的坏字符跳转概念,是BM算法能够在一般应用场景中,效率高于KMP算法的主要原因。WM算法在多模式匹配中,也引入了类似的概念,从而实现了模式匹配中的大幅度跳转。但是在单模式应用场景,很少有哪个模式串会包含所有可能的输入字符,而在多模式匹配场景,如果模式集合的规模较大的话,很可能会覆盖很大一部分输入字符,导致坏字符跳转没有用武之地。所以WM算法中使用的坏字符跳转,不是指一个字符,而是一个字符块,或者说几个连续的字符。通过字符快的引入,扩大了字符范围,使得实现坏字符跳转的可能性大大增加。

WM算法一般由三个表构成,SHIFT,HASH,PREFIX。

SHIFT表就相当于BM算法中的坏字符表,其构建过程有如下几点需要关注

  • 我们对模式集合中所有模式的前m个字符构建SHIFT表,其中的m,是模式集合中最短模式的长度值。
  • 对于字符块的长度B的选择,我们一般选择2,3个字节。
  • 在构建SHIFT表的时候,对一个模式p的前m个字符,我们要处理其所有长度为B的子串,并填充对应的SHIFT值,假设字符块大小为B,当前字符快的尾字符与模式前缀的末尾距离为n,则SHIFT[p]=n。以模式abcdefgh为例,假设要处理其前6个字符构成的子串,那么SHIFT[ab]=4,SHIFT[bc]=3,SHIFT[cd]=2,SHIFT[de]=1,SHIFT[ef]=0都要加入SHIFT表中。
  • 如果多个模式串前缀,或者同一模式前缀中,有相同的字符块,则保留其中SHIFT值的最小者。比如模式串p1 = abcab,p2=dcabe,其中对于块ab可以计算出三个SHIFT值3,1,0,这里我们需要保留SHIFT[ab]=0。

HASH表就是对应字符块B,所有SHIFT[B]=0的模式与B的映射关系。比如模式串abcde,bcbde,对于块de,他们的SHIFT值都是0,所以他们都由de索引。

实际上,在WM算法中,是可以没有PREFIX表的,但是对于字母文字来说,可能存在多个模式由一个字符块共同索引的情况,如上例,如果存在10个最末两个字符为de的模式串的话,那么在目标串中检索出de组合之后,要用当前的子串逐个尝试匹配这10个模式串,对于算法效率影响很大。所以WM算法同时截取了模式串的一个长度为2或者3的前缀,构建PREFIX表。在执行中缀查找的基础之上,再执行前缀查找,缩小备选模式集,提高匹配效率。如上例,abcde,bcbde,有共同的字符块de,使得SHIFT[de]=0,如果没有PREFIX表,就需要将游标向前移动5位,然后逐一尝试匹配这两个备选模式,如果有了PREFIX表,我们就可以用两个模式的前缀ab,bc再执行一次索引,一般情况下SHIFT值相同,PREFIX也相同的模式串比例很小,本例中二者的前缀是不同的,索引之后就只剩下一个备选模式,此时执行一次字符串比较即可判断当前位置是否发现了匹配模式。

下面用图来描述一下WM算法所要维护的数据结构:

WM算法执行模式匹配的流程如下。

对于目标串target,游标i,模式前缀长度m,字符快长度B,前缀长度C。我们取target[i-B+1...i],查找其在SHIFT表中的对应值SHIFT[target[i-B+1...i]],如果找不到,则i+=m-B+1,如果其值为c(c != 0),那么我们i+=c,再执行上述操作。如果其SHIFT值等于0,我们需要取出target[i-m+1...i-m+C],然后在SHIFT[de]=0对应PREFIX结合中查找PREFIX[target[i-m+1...i-m+C]],如果不存在,则将游标i+=1。如果存在则用target[i-m+1]开始的子串,依次匹配满足条件的所有模式串,直到找到匹配模式,或者未发现匹配位置。然后i+=1,继续向后查找。

下面以WM算法对目标串target[1...10]=dcbacabcde,模式结合P{abcde,bcbde,abcabe},的匹配过程来形象的说明一下。首先,对于模式集合P预处理之后的结果如上面的程序结构图所示。然后从i=5开始执行算法。首先我们发现target[4...5] = ac,SHIFT表中不存在ac,所以i+4,到target[9],此时发现target[8...9]=cd,SHIFT[cd]=1,所以i+1,然后发现target[9...10]=0,我们取target[6...7]=ab,发现PREFIX[ab]对应的模式串是abcde,然后我们从target[6]开始用目标串与模式串比较,发现匹配模式abcde。

后继:

既然WM算法也是堪称经典的多模式匹配算法,就不能回避与AC算法的比较。

  • 从算法的效率角度讲,按照Wu Sun,Manber两人的论文所列的数据,WM算法的性能要明显优于AC算法。
  • 从模式集预处理的角度讲,两者的预处理过程都很复杂,但是AC算法由于要维护一个状态机,所以在构建的时间和空间复杂度上,还是要比WM算法更消耗资源。而且AC算法既然要维护自动机,就不得不考虑用什么样的算法来实现自动机的问题,所以自动机的实现方式也会影响AC算法的效果,而WM算法需要考虑的问题就要简单多,一个给力的字典结构足矣。
  • 从模式集角度讲,如果模式集动态可变,AC算法动态调整自动机的成本可能要比WM算法高很多。
  • 从程序适应性的角度讲,AC算法对于任何模式结合,任何目标串都是O(n)的时间复杂度,而WM算法,对于某些模式集和目标串可能会发生退化现象。

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

分类: Web Security/Javascript Algorithms2009-05-21 14:56 8095人阅读 评论(7) 收藏 举报
算法distancefunctionalgorithmeach存储

多模式匹配的用法,多了去了!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<HASH[h+1])看是否PREFIX[p]=text_prefix。如果相等,方才让真正的pat(即PAT_POINT[p])去和text匹配。

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

//开始匹配

while(text<textend)

{

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<B;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多关键词匹配算法”

Wu-Manber 经典多模式匹配算法相关推荐

  1. [数据结构]模式匹配算法--KMP算法详解

    目录 一. 模式匹配 二. 模式匹配算法 1. 朴素模式匹配算法 2. KMP算法 1). KMP算法的优势 2). KMP算法的原理 3). next数组的构造 4). 利用next数组匹配的过程 ...

  2. 字符串的模式匹配 (朴素模式匹配算法 ,KMP算法)

    字符串的模式匹配 寻找字符串p在字符串t中首次出现的起始位置 字符串的顺序存储 typedef struct {char str[MAXSIZE];int length; }seqstring; 朴素 ...

  3. Jsp+Servlet+JavaBean经典MVC模式理解

    MVC模式目的(实现Web系统的职能分工). 在Java EE中,Jsp+Servlet+JavaBean算是里面经典的模式,是初学者必备的知识技能.M, Model(模型)实现系统的业务逻辑 1.通 ...

  4. java 模式匹配算法_用Java匹配模式

    java 模式匹配算法 如果您使用的是Java,那么您很有可能以前已经看过它的模式匹配. String#matches(String)方法在内部使用Pattern类型,该类型包含更复杂的功能: 通过编 ...

  5. 第四章:2.串 -- 串的模式匹配算法(KMP)

    前言: 目录: 1.串类型的定义 2.串的表示和实现 3.串的模式匹配算法 4.串操作应用举例 正文: 串的模式匹配即,在给定主串S 中,搜索子串T 的位置,如果存在T 则返回其所在位置,否则返回 0 ...

  6. 图解字符串的朴素模式匹配算法

    复习串的朴素模式匹配算法 模式匹配 : 子串定位运算,在主串中找出子串出现的位置. 在串匹配中,将主串 S 称为目标(串),子串 T 称为模式(串).如果在主串 S 中能够找到子串 T, 则称匹配成功 ...

  7. KMP算法--字符串模式匹配算法

    今天看到第四章<串>了,其中我觉得花的时间多一点的值得我写篇随笔的知识点就是:4.3 串的模式匹配算法:书上介绍了两种字符串匹配算法,一种是最简单最容易想到的逐个字符匹配算法(时间复杂度在 ...

  8. 解析BF(普通串模式匹配算法)算法

    写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站.博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事 ...

  9. 数据结构——基于字符串模式匹配算法的病毒感染检测

    实验四 基于字符串模式匹配算法的病毒感染检测 [实验目的] 1.掌握字符串的顺序存储表示方法. 2.掌握字符串模式匹配BF算法和KMP算法的实现. [实验内容] 问题描述 医学研究者最近发现了某些新病 ...

最新文章

  1. 用神经网络分类随机数与无理数
  2. 蓝桥杯练习(java):字符串对比
  3. DRBD+HeartBeat+NFS 架构
  4. 关于MySQL内存泄露如何排查的一些思路
  5. MATLAB基本二维曲线绘制
  6. 无心剑中译奥修《奥修对爱与婚姻的印象》
  7. React之组件小析
  8. POJ 1014 Dividing【多重背包+二进制优化】
  9. 六千字干货文:到底要怎么去学算法?
  10. [ZT]用CSC.exe来编译Visual C#的代码文件,解释CSC参数和开关的具体作用
  11. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码]...
  12. camera 驱动 raw bayer isp
  13. 时间管理四象限法则该如何理解
  14. python爬虫国内外研究现状
  15. 各类光纤接口类型的区别与图示
  16. hao360恶意篡改IE首页——修复方法
  17. (差分)洛谷P4231 三步必杀
  18. 动感歌词制作与转换工具
  19. 腾讯云数据库SaaS致力于构建数据库分布式云,为更多更广的用户提供服务
  20. 规则在自然语言处理领域的重要地位

热门文章

  1. 解决红旗linux6SP2 鼠标单击变双击的问题
  2. 【跑飞、死机】单片机 msp430程序跑飞原因和解决方式积累
  3. 如何把一幅照片 PS 成动漫风格
  4. Komodo Edit 11在windows 10上平台下的安装教程——不看后悔系列
  5. 6108v9c卡刷包_wc6108v9c线刷包_wc6108v9c刷机包_wc6108v9c固件包_wc6108v9c救砖包 - 线刷宝ROM中心...
  6. WGS84GPS坐标与大地坐标系互相转化
  7. keil stm32f407工程环境搭建
  8. 服务器128g内存显示64g,64g内存服务器
  9. 基于MFC(VC++)的汽车修理管理系统毕业设计
  10. Gartner发布2020年中国ICT技术成熟度曲线