Boyer-Moore高质量实现代码详解与算法详解html
鉴于我见到对算法自己分析很是透彻的文章以及实现的很是精巧的文章,因此就转载了,本文的贡献在于将二者结合起来,方便你们了解代码实现!node
本文转自http://www.cnblogs.com/xubenben/p/3359364.html,感谢做者的总结,本人也对其进行部分修改算法
C语言代码实现转自:ide
http://www-igm.univ-mlv.fr/~lecroq/string/node14.html网站
另外,网站http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/fstrpos-example.html有个关于BM算法的详细例子,看看挺好的。idea
BM算法的论文在这儿http://www.cs.utexas.edu/users/moore/publications/fstrpos.pdfspa
BM算法设计
后缀匹配,是指模式串的比较从右到左,模式串的移动也是从左到右的匹配过程,经典的BM算法实际上是对后缀蛮力匹配算法的改进。因此仍是先从最简单的后缀蛮力匹配算法开始。下面直接给出伪代码,注意这一行代码:j++;BM算法所作的惟一的事情就是改进了这行代码,即模式串不是每次移动一步,而是根据已经匹配的后缀信息,从而移动更多的距离。code
j = 0; while (j <= strlen(T) - strlen(P)) { for (i = strlen(P) - 1; i >= 0 && P[i] ==T[i + j]; --i) if (i < 0) match; else j++; }
为了实现更快移动模式串,BM算法定义了两个规则,好后缀规则和坏字符规则,以下图能够清晰的看出他们的含义。利用好后缀和坏字符能够大大加快模式串的移动距离,不是简单的++j,而是j+=max (shift(好后缀), shift(坏字符))
先来看如何根据坏字符来移动模式串,shift(坏字符)分为两种状况:
坏字符没出如今模式串中,这时能够把模式串移动到坏字符的下一个字符,继续比较,以下图:
坏字符出如今模式串中,这时能够把模式串第一个出现的坏字符和母串的坏字符对齐,固然,这样可能形成模式串倒退移动,以下图:
此处配的图是不许确的,由于显然加粗的那个b并非”最靠右的”b。并且也与下面给出的代码冲突!我看了论文,论文的意思是最右边的。固然了,尽管一时大意图配错了,论述仍是没有问题的,咱们能够把图改正一下,把圈圈中的b改成字母f就行了。接下来的图就再也不更改了,你们内心有数就好。
为了用代码来描述上述的两种状况,设计一个数组bmBc['k'],表示坏字符‘k’在模式串中出现的位置距离模式串末尾的最大长度,那么当遇到坏字符的时候,模式串能够移动距离为: shift(坏字符) = bmBc[T[i]]-(m-1-i)。以下图:
数组bmBc的建立很是简单,直接贴出代码以下:
void preBmBc(char *x, int m, int bmBc[]) {// int i; for (i = 0; i < ASIZE; ++i) bmBc[i] = m; for (i = 0; i < m - 1; ++i) bmBc[x[i]] = (m - 1) - i;//距离模式串尾部的距离。 }
代码分析:
ASIZE是指字符种类个数,为了方便起见,就直接把ASCII表中的256个字符全表示了,哈哈,这样就不会漏掉哪一个字符了。
第一个for循环处理上述的第一种状况,这种状况比较容易理解就很少提了。
第二个for循环,bmBc[x[i]]中x[i]表示模式串中的第i个字符。
bmBc[x[i]] = m - i - 1;也就是计算x[i]这个字符到串尾部的距离。
为何第二个for循环中,i从小到大的顺序计算呢?哈哈,技巧就在这儿了,缘由在于就能够在同一字符屡次出现的时候以最靠右的那个字符到尾部距离为最终的距离。固然了,若是没在模式串中出现的字符,其距离就是m了。
再来看如何根据好后缀规则移动模式串,shift(好后缀)分为三种状况:
模式串中有子串匹配上好后缀,此时移动模式串,让该子串和好后缀对齐便可,若是超过一个子串匹配上好后缀,则选择最靠左边的子串对齐。
模式串中没有子串匹配上后后缀,此时须要寻找模式串的一个最长前缀,并让该前缀等于好后缀的后缀,寻找到该前缀后,让该前缀和好后缀对齐便可。
模式串中没有子串匹配上后后缀,而且在模式串中找不到最长前缀,让该前缀等于好后缀的后缀。此时,直接移动模式到好后缀的下一个字符。
为了实现好后缀规则,须要定义一个数组suffix[],其中suffix[i] = s 表示以i为边界,与模式串后缀匹配的最大长度,以下图所示,用公式能够描述:知足P[i-s, i] == P[m-s, m]的最大长度s。
构建suffix数组的代码以下:
void suffixes(char *x, int m, int *suff) //suff数组中记录了,模式串中i位置前suff[i]的部分可以和后缀最大的匹配以下图 { //m为模式串的长度 suff[m-1]=m; //suff[m-1]自己就是m for (i=m-2;i>=0;--i){//开始从尾巴处开始计算 q=i;//最终计算的q为模式串中能和最后的后缀匹配的起始位置。以下图q=1及p[1]=b while(q>=0&&x[q]==x[m-1-i+q]) //只要q大于0,这里-i+q其实就是字符串匹配的长的负值 --q; //只要一个匹配后,就将值减1去看前一个字符是否匹配。 suff[i]=i-q; } }
注解:这一部分代码乏善可陈,都是常规代码,这里就很少说了。
有了suffix数组,就能够定义bmGs[]数组,bmGs[i] 表示遇到好后缀时,模式串应该移动的距离,其中i表示好后缀前面一个字符的位置(也就是坏字符的位置),构建bmGs数组分为三种状况,分别对应上述的移动模式串的三种状况
模式串中有子串匹配上好后缀
模式串中没有子串匹配上好后缀,但找到一个最大前缀
模式串中没有子串匹配上好后缀,但找不到一个最大前缀
构建bmGs数组的代码以下:
void preBmGs(char *x, int m, int bmGs[]) {//bmGs[i] 表示遇到好后缀时,模式串应该移动的距离 int i, j, suff[XSIZE]; suffixes(x, m, suff); for (i = 0; i < m; ++i) bmGs[i] = m; j = 0; for (i = m - 1; i >= 0; --i) if (suff[i] == i + 1)//这里很巧妙,能够看看后面介绍,大意就是匹配到状况2了,前缀匹配了。 for (; j < m - 1 - i; ++j) if (bmGs[j] == m)//这里设置这个条件就是只容许修改一次,由于这个i从大变小,防止出现状况一 后又出现了状况二,而改变bmGs的数组值。 bmGs[j] = m - 1 - i; for (i = 0; i <= m - 2; ++i) bmGs[m - 1 - suff[i]] = m - 1 - i; }
注解:
这一部分代码挺有讲究,写的很巧妙,这里谈谈个人理解。讲解代码时候是分为三种状况来讲明的,其实第二种和第三种能够合并,由于第三种状况至关于与好后缀匹配的最长前缀长度为0。
因为咱们的目的是得到精确的bmGs[i],故而若一个字符同时符合上述三种状况中的几种,那么咱们选取最小的bmGs[i]。好比当模式传中既有子串能够匹配上好后串,又有前缀能够匹配好后串的后串,那么此时咱们应该按照前者来移动模式串,也就是bmGs[i]较小的那种状况。故而每次修改bmGs[i]都应该使其变小,记住这一点,很重要!
而在这三种状况中第三种状况得到的bmGs[i]值大于第二种大于第一种。故而写代码的时候咱们先计算第三种状况,再计算第二种状况,再计算第一种状况。为何呢,由于对于同一个位置的屡次修改只会使得bmGs[i]愈来愈小。
代码4-5行对应了第三种状况,7-11行对于第二种状况,12-13对应第三种状况。
第三种状况比较简单直接赋值m,这里就很少提了。
第二种状况有点意思,我们细细的来品味一下。
1. 为何从后往前,也就是i从大到小?
缘由在于若是i,j(i>j)位置同时知足第二种状况,那么m-1-i<m-1-j,而第十行代码保证了每一个位置最多只能被修改一次,故而应该赋值为m-1-i,这也说明了为何要 从后往前计算。
2. 第8行代码的意思是找到了合适的位置,为何这么说呢?
由于根据suff的定义,咱们知道
x[i+1-suff[i]…i]==x[m-1-siff[i]…m-1],而suff[i]==i+1,咱们知道x[i+1-suff[i]…i]=x[0,i],也就是前缀,知足第二种状况。
3. 第9-11行就是在对知足第二种状况下的赋值了。第十行确保了每一个位置最多只能被修改一次。
第12-13行就是处理第一种状况了。为何顺序从前到后呢,也就是i从小到大?
缘由在于若是suff[i]==suff[j],i<j,那么m-1-i>m-1-j,咱们应该取后者做为bmGs[m - 1 - suff[i]]的值。
再来重写一遍BM算法:
void BM(char *x, int m, char *y, int n) { int i, j, bmGs[XSIZE], bmBc[ASIZE]; /* 初始化这两个数组 */ preBmGs(x, m, bmGs); preBmBc(x, m, bmBc); /* Searching */ j = 0; while (j <= n - m) { for (i = m - 1; i >= 0 && x[i] == y[i + j]; --i); if (i < 0) { OUTPUT(j); j += bmGs[0]; } else j += MAX(bmGs[i], bmBc[y[i + j]] - m + 1 + i);//这里j的变化依据坏字符和好后缀所给出的能够移动的最大值 去移动。 } }