字符串匹配算法BF BM BMH BMHS分析Word文档格式.docx
《字符串匹配算法BF BM BMH BMHS分析Word文档格式.docx》由会员分享,可在线阅读,更多相关《字符串匹配算法BF BM BMH BMHS分析Word文档格式.docx(11页珍藏版)》请在冰豆网上搜索。
{k++;
j++;
}
Else
{
k=k-j+1;
}
if(j==m)Matchfoundattext[k-m]
elseNomatchfound
例子1:
astringsearchingexamplelienvolingrelatively
模式串:
relative
1.astringsearchingexamplelienvolingrelatively
relative
2.astringsearchingexamplelienvolingrelatively
3.astringsearchingexamplelienvolingrelatively
4.astringsearchingexamplelienvolingrelatively
:
32.astringsearchingexamplelienvolingrelatively
该算法简单,但是效率较低。
时间复杂度:
设匹配成功发生在text[i]处,则在i-1趟不成功的匹配中比较了(i-1)
m次,第i趟成功匹配共比较了m次,所以总共比较了i
m次,因此平均比较次数是:
m*(n-m+1)/2。
最坏情况下要进行m*(n-m+1)次比较,时间复杂度为O(m*n)。
2.2KMP算法
由Knuth等人提出的Knuth-Morris-Praa(KMP)算法[2]是对BF算法的很大改进,每次匹配失败时利用已经得到的“部分匹配”结果,将模式串向右移动若干位置后继续比较。
KMP算法的基本思想:
将文本text和模式pat左端对齐进行比较,匹配text[i]和pat[j]时:
若text[i]=pat[j],则继续往前匹配比较;
若text[i]
pat[j],则文本中i不变,模式中j指向next[j]所指示位置。
这可以提高匹配算法的效率,但相对比较复杂。
KMP算法与BF算法在形式上极为相似。
不同之处在于:
当匹配过程中产生“失配”时,指针i不变,指针j退回到next[j]所指示的位置上重新进行比较,并且当指针j退至零时,指针i和指针j需同时增1。
即若主串的第i个字符和模式的第一个字符不等,应从主串的第i+1个字符起重新进行匹配。
实现过程:
循环直到text中所剩字符小于pat的长度或T的所有字符均比较完(如果text[i]=pat[j],则继续比较text和pat的下一个字符;
否则将j向右滑动到next[j]位置,即j=next[j];
如果j=0,则将i和j分别+1,准备下趟比较);
具体算法如下:
AlgorithmKMP
intKMP(chartext[],charpat[],intnext[])
{
inti=0,j=0;
n=len(text);
m=len(pat);
while(i<
n&
j<
m)
if(j==-1||pat[j]==text[i])
{i++;
j++;
elsej=next[j]
if(j>
=m)returni-m;
elsereturn0;
数组next[j]表示当模式中第j个字符与正文中相应字符匹配失败时,在模式中需要一个新的和正文中该字符进行比较的字符的位置,这一位置只与模式本身有关,而与正文无关。
求解模式的next函数的算法如下:
get_next(charpat[],intnext[],intm)
intj,k;
k=-1;
next[0]=-1;
while(j<
if(k==-1||pat[j]==pat[k])
k++;
next[j]=k;
elsek=next[k];
}
根据以上算法,以pat[]=‘abcac’和text[]=‘ababcabcacbab’为例,开始时,把模式串和正文左边对齐。
算法从左至右进行匹配,第一次匹配结束时,i=3,j=next[j]=3。
将模式串向右移动,使得text[i]和pat[j]对齐。
继续比较,如图2.1所示。
图2.1KMP算法匹配过程
依上述的方法继续进行比较,直到匹配成功或与正文比较结束为止。
Ο(n
m),当m<
<
n时,KMP算法的时间复杂性是Ο(n)。
2.3BM算法
1977年Boyer和Moore提出了全新的算法——Boyer-Moore(简称BM)算法[1],它从另外一个角度出发提出一种比较新颖的方法来求解模式匹配问题。
BM算法和KMP算法的差别是对模式串的扫描方式由自左至右变成自右至左,另一个差别是考虑正文中可能出现的字符在模式中的位置。
这样做的好处是当正文中出现模式串中没有的字符时,就可以将模式串大幅度划过正文。
匹配过程中模式从左向右移动,但字符的比较却从右向左,即按p[m-1],p[m-2],…p[0]的次序进行比较,在发现不匹配时,算法根据预先计算好的两个数组,将模式串向右移动尽可能远的距离,这两个数组称为shift和skip。
“坏字符”思想:
定义skip数组,如果字符ch没有在pattern中出现,skip[ch]=m;
如果字符ch在pattern中出现过,记e表示ch在pattern中出现的最后位置(下标从0开始),则skip[ch]=m-e-1。
从右向左地把模式串同文本作比较,设匹配进行到比较text[k-m+1…k]和pattern[0…m-1],从右至左一次检查text[k],text[k+1]…text[k-m+1]当匹配失败发生在text[i]!
=pattern[j](i在k-m+1到k之间,j在0到m-1之间)时,采用skip[text[i]]表示扫描文本的指针向前移动的位数。
“坏字符”思想实际上是将text[i]在pattern中最后一次出现的位置与文本中的text[i]对齐后开始新的一轮匹配。
“好尾缀”思想:
若pattern后部与text一致的部分之中,有一部分在pattern中其它地方出现,则可以将pattern向右移动,直接使这部分对齐,且要求这一部分尽可能的大。
其基本算法和KMP中的next函数比较相似。
BM算法的基本思想:
模式串P和给定的文本T左端对齐,在扫描的过程中采用自右向左的扫描方式,一旦出现不匹配现象(失配),就可以使模式串P向右滑动一段距离,在滑动过程中采用了坏字符方法和好后缀方法,滑动的距离是两者之中的最大值。
现对BM算法详细描述如下:
模式串P和给定的文本T左端对齐,自右向左开始扫描对比,如果匹配则继续向左扫描如果能够到达最左端,则说明匹配成功,如果扫描过程中遇到不匹配字符,则根据坏字符函数和好后缀函数中滑动距离较大的来使模式串右移,然后重复上面的操作。
坏字符函数的功能是:
如果模式串P中某个字符P[i]和文本串中某个字符T[j]失配,而且T[j]不出现在模式串P中,那么模式串P右移至T[j]右边第一位,即P[1]等于T[i+1];
若在两者失配的同时,T[j]在P中出现(可能不止一次),则将T[j]和P中最右边的相等的字符对齐。
好后缀函数的功能是:
如果模式串P中后面的k位已经和T中有一部分匹配成功(标记匹配成功的部分为P1子串),此时检查模式串P中前边m-k位是否存在P1子串,若存在,则将模式串P右移使之对齐。
如果不存在P1子串,则检查在模式串P中前边m-k位是否存在P1子串的任意子串,如果存在,则右移模式串使其对齐。
如果两者皆不存在,此时可直接将模式串向右移动m,BM算法在匹配失败的时候将模式串向右移动,通过坏字符函数和好后缀函数来确定移动的长度,具有了较高的匹配速率。
但是其算法在好后缀函数的处理上面非常难以理解和实现,而且需要将好后缀函数和坏字符函数获得的模式串移动的值进行比较,取其最大值,因此匹配速率也是可以再提高的。
假定失配发生时的情况如图2.2所示,此时已经匹配的部分为u,正文的b字符与模式的a字符发生失配。
图2.2失配时的情况
shift函数通过对已经匹配部分的考查决定模式右移的长度,失配发生时,如果u在模式中再次出现且前缀不是a字符(例如c字符),则将模式串右移使得正文中的u与满足上述条件的最右边的u对齐。
如图2.3所示,如果u在模式中没有再次出现或者再次出现但前缀是a字符,则不能按照上述方法右移。
如果模式存在一个前缀v,该v也是u的后缀,那么找到这样一个最长的v,将模式串右移,使模式前缀v与正文中u的后缀v对齐,如图2.4所示,在极端情况下,不存在这样的v,模式串将跳过u。
图2.3shift的第一种情况
图2.4shift的第二种情况
skip函数需要考查造成失配的正文字符(此时为b)在模式中出现的情况,如果b在模式中存在,则右移模式使正文b与模式中最右边的b对齐。
如图2.5所示,如果b在模式中不出现,则将模式右移跳过字符b,如图2.6所示。
注意skip有可能是负值,因此在移动模式的时候,BM算法选取shift和skip的最大值。
图2.5skip的第一种情况
图2.6skip的第二种情况
下面给出Boyer-Moore算法:
AlgorithmBM
k=m-1;
while(k<
n)
j=m-1;
while((j>
=0)&
(pat[j]==text[k]))
j--;
k--;
if(j==-1)thenmatchfoundattaxt[k]
elsek=k+max(skip[text[k]],shift[k])
nomatchfound
例子2:
1.which-finally-halts--at-that-point
at-that(fnotinpattern)
2.which-finally-halts--at-that-point
at-that(-inpattern)
3.which-finally-halts--at-that-point
at-that(lnotinpattern)
4.which-finally-halts--at-that-point
at-that
5.which-finally-halts--at-that-point
at-that
skip数组对于字符空间较小的情况效果并不显著,但当字符空间很大而模式串较短时,例如字符空间为ASCII表时,将变得非常有效。
BM算法的平均时间复杂度为O(n/m)。
BM算法采用“跳跃式”查找策略,因此该算法在比较字符的次数上比传统的模式匹配算法少,因此在入侵检测系统中得到了广泛的应用。
在模式匹配中存在两个基本定理:
任何字符串匹配算法在最坏情况下必须检查至少n-m+1个文本中的字符;
任何字符串匹配算法至少检查n/m个字符。
因此,没有一个算法比BM算法有更好的计算复杂度。
但是我们可以通过改进来减少比较次数,提高匹配的平均性能。
2.4BMH算法
BM算法为了确定在不匹配的情况下最大的可移位距离而是用了两种启发,即“坏字符”启发和“好后缀”启发,两种启发均能导致最大为m的移动距离。
但是,忧郁“好后缀”启发的预处理和计算过程都比较复杂,Horspool于1980年发表了改进与简化BM算法的论文,即Boyer-Moore-Horspool(BMH)算法[9]。
BMH算法在移动模式时仅考虑了“坏字符”策略。
它首先比较文本指针所指字符和模式串的最后一个字符,如果相等再比较其余m-1个字符。
无论文本中哪个字符造成了匹配失败,都将由文本中和模式串最后一个位置对应的字符来启发模式向右的移动。
即当匹配开始比较text[k-m+1…k]和pattern[0…m-1]时,从右至左依次检查text[k],text[k-1]…text[k-m+1],一旦发现不匹配,就将文本指针重新赋值为k+skip[text[k]](k是一个中间变量,表示文本中每次从右至左开始比较的起始位置)。
关于“坏字符”启发和“好尾缀”启发的对比,在文献[5]中的研究表明:
“坏字符”启发在匹配过程中占主导地位的概率为94.03%,远远高于“好尾缀”启发。
在一般情况下,BMH算法比BM有更好的性能,它简化了初始化过程,省去了计算“好尾缀”启发的移动距离,并省去了比较“坏字符”和“好尾缀”的过程。
算法将失配与计算右移量独立,计算右移量时并不关心正文中哪个字符造成了失配,而是简单的使用正文中与模式最右端字符对齐的字符来决定右移量,即只使用skip数组。
BMH算法的具体描述如下:
AlgorithmBMH
for(i=0;
i<
maxchar;
i++)
skip[i]=m;
for(i=0;
m-1;
skip[pat[i]]=m-i-1
k=m-1;
while(k<
n)
j=m-1;
while((j>
=0)&
(pat[j]==text[k]))
j--;
if(j==-1)thenmatchfoundattext[k]
elsek=k+skip[text[k]]
}nomatchfound
例子3:
1.astringsearchingexamplienvolingrelatively
relative(snotinpattern)
2.astringsearchingexamplienvolingrelatively
relative(gnotinpattern)
3.astringsearchingexamplienvolingrelatively
relative(iinpattern)
4.astringsearchingexamplienvolingrelatively
relative(nnotinpattern)
5.astringsearchingexamplienvolingrelatively
relative(rinpattern)
6.astringsearchingexamplienvolingrelatively
根据BMH算法我们再以text=‘abhdgfdabbdbdabdbfd’,pattern=‘abdbfd’字串为例,开始匹配时,把模式串与正文自左边对齐。
使用BMH算法经过6次循环后匹配成功。
当字符集元素个数为s时,BMH算法预处理时间复杂度为O(m+s),空间复杂度为O(s)。
BMH算法在最好的情况下的复杂度是O(n/m),最坏的情况下的复杂度为O(mn),但在一般情况下比BM有更好的性能。
它只使用了一个数组,简化了初始化过程,省去了求最大值的计算。
匹配过程如表2.1所示:
表2.1BMH算法匹配过程
文本abhdgfdabbdbdabdbfd指针移动
1.abdbfdskip[‘f’]=12.abdbfdskip[‘d’]=33.abdbfdskip[‘b’]=24.abdbfdskip[‘b’]=25.abdbfdskip[‘a’]=56.abdbfd
BMH算法在一般情况下由于省去了求最大值的计算,因而比BM有更好的性能。
2.5BMHS算法
1990年,Sunday在BMH算法的基础上又提出了改进,提出了BMHS算法[10],其主要改进思想是:
在计算skip数组时,考虑下一个字符的情况,即利用下一个字符决定右移量。
若下一个字符不出现在模式中,BMHS算法的右移量比BMH算法的大,若text[i]不出现在模式中,而text[i+1]出现在模式中时,则BMHS算法效果不如BMH算法,但在通常情况下BMHS算法实现速度大于BMH算法,它在最好情况下的时间复杂度为O(n/m+1)。
BMHS算法如下:
AlgorithmBMHS
maxchar;
skip[i]=m+1;
for(i=0;
m;
i++)
skip[pat[i]]=m-1;
while(k<
While((j>
(pat[j]==text[k+j-(m-1)]))
j--;
if(j==-1)thenmatchfoundattext[k]
elsek=k+skip[text[k+1]]
Nomatchfound
例子4:
1.astringsearchingexamplienvolingrelatively
relative(einpattern)
2.astringsearchingexamplienvolingrelatively
relative(ainpattern)
3.astringsearchingexamplienvolingrelatively
relative(nnotinpattern)
4.astringsearchingexamplienvolingrelatively
5.astringsearchingexamplienvolingrelatively
6.astringsearchingexamplienvolingrelatively
relative
7.astringsearchingexamplienvolingrelatively
BMHS算法用下一个字符来计算右移量,即只使用text[i+1],当下一个字符不在模式中出现时,它的右移量比BMH算法的右移量大,通常情况下,BMHS算法比BMH算法快,当text[i]不在模式中出现,而text[i+1]却出现在模式中时,即当skip[i]>
skip[i+1]时,BMHS算法的效果就不如BMH算法。
2.6本章小结
在以上介绍的算法中,BF算法思路简单,效率太低;
KMP算法引入next函数,思路新颖,效率也较之提高;
BM算法考虑比较全面,包括了右移时的各种情况,但它使用了两个数组,预处理时间花费较大;
BMH算法在BM算法的基础上进行了简化,只使用一个数组来计算右移量,使得算法更加简单、快速、高效;
BMHS算法在BMH算法的基础上又提出考虑下一个字符,用下一个字符来决定右移量,使右移量进一步增大,算法速度更快,但在某些情况下,它的效果不如BMH算法。
各种模式匹配算法的目的都在于,当模式串与文本串发生失配时模式串可以向右移动尽可能大的距离,以减少比较以及窗口移动次数。
经以上对几种单模式匹配算法的分析比较,可以发现BM算法考虑较为全面,但它使用了2个函数来计算最大右移量,导致预处理时间开销较大,BMH算法和BMHS算法在其基础上进行了有效的简化和改进。
随着研究的深入,之前的算法都有写不足之处,还有更好的算法吗?
根据以上的分析,将BMH和BMHS算法的优点进行综合,在此基础上加以改进,便得到了一个效果更好、速度更快的算法,我们将这个改进算法称为BMH2C。