之前我的《BM算法详解》一文中有一个巨大的缺憾,就是没能给出计算模式串好后缀跳转表的高效算法。Robert S.Boyer和J Strother Moore两人的论文中,不知什么原因,并没有给出这样的算法,蛮力算法O(n^3)的时间复杂度使得BM算法的实用性大打折扣。实际上线性时间内计算出模式串的好后缀跳转表的算法是存在,但是在介绍这个算法之前,我要向大家推荐一本字符串处理方面的权威著作《Algorithms on Strings,Trees and Sequences》,作者Dan Gusfield。书中几乎涵盖了当今具有实用价值的所有字符串处理技术,当然BM和KMP算法也涵盖其中,本文的内容就源于此书。不过这本书的内容可以说是非常非常的难,要想全部吃透十分不易。

在我的有关KMP,BM算法的两篇文章中,我已经提到了一个关键的问题,那就是前/后缀的自包含问题。无论是KMP算法还是BM算法的跳转表,都与自包含前/后缀有着直接的联系。这里我们需要引入一个概念Zi(S),其中S代表模式串,对于模式串S[1...n],Zi(S)表示子串S[i...j]的长度,其中j是所有满足S[i...j]=S[1...j-i+1]的j中的最大者。说起来挺玄乎,实际就是以i为起始的最长包含前缀。对于S=aabcaabxaaz,我们有

  • Z5(S)=3,(aab)c(aab)xaaz
  • Z6(S)=1,(a)abca(a)baaz
  • Z7(S)=Z8(S)=0,当S[i]!=S[1]时Zi(S)=0
  • Z9(S)=2,(aa)bcaabx(aa)z

由上面Z5(S)=3我们知道S[5...7]=S[1...3],且S[5...8]!=S[1...4],这里我们把S[5...7]叫做字符串S的一个Z-block,对于Zi(S),如果Zi(S)!=0,那么所标记的Z-block起始于i,结束于i+Zi(S)-1。显然,一个字符串可能包含若干个Z-block,而且各Z-block之间可能互相交叠。我们再定义两个值li,ri,其中li,ri是包含S[i]的所有Z-block中右端点最大的一个,如下图所示,这里包含i的Z-block有两个,只有标注a的Z-block的l值和r值,才是li和ri的实际值。实际上S[li...ri]=S[1...ri-li+1]。

现在我们就来介绍一下,在Z1(S),……,Zi(S),li,ri已知的情况下,如何求解Zi+1(S),这里我们令li=l, ri=r, i+1=k, i-li+2=k'。

1. 如果k,Zk'(S)与l,r的所决定的Z-block关系如下图所示,因为S[l...r]=S[1...r-l+1],所以我们可以把S[l...r]区间内的问题,放到S[1...r-l+1]区间内来考虑,此时k在1,r-l+1区间内的对应点就是k'。我们需要关注Zk'(S)这个已知量,在下图所示的这种情况中,Zk'(S)所决定Z-block完全包含在1,r-l+1区间内。也就是k'+Zk'(S)-1<r-l+1,此时Zk(S)实际上就等于Zk'(S)。

2. 如果k,Zk'(S)与l,r的所决定的Z-block关系如下图所示。此时,我们也同样将S[l...r]区间内的问题,放到S[1...r-l+1]区间内来分析。此时Zk'(S)所决定的Z-block的右端要超过r-l+1,也就是说对于Zk(S),我们已经知道其前r-k+1个元素与S[1...r-k+1]相同,但是对于S[r]以后的元素是否还可以与前面的r-k+1个元素连起来形成更长的包含前缀我们还只有进行比较后才能知道。由于之前我们的已经有S[k...r]=S[k'...r-l+1]=S[1...r-k+1](注意图中几个标注beta的区域),所以我们可以省去对这两个区间的比较,直接从S[r-k+2]开始与S[r-k+2]进行比较,直到匹配失败为止,此时我们就得到新的右端点ri+1,同时将li+1更新为i+1。

3. 如果r<=k。那么之前计算出的Z-block对我们没有任何帮助,我们从r开始,找到最小的k,使得S[r...k]!=S[1...r-k+1]。此时我们同时还要更新对应的li+1=i+1,ri+1=k-1。

分别处理上述三种情况,我们就可以在线性时间内,递推填写S[1...n]的所有Zi(S)值。假设模式串S="aabaabcaxaabaabcy",其对应的Zi(S)值如下表。

   1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17
S a a b a a b c a x a a b a a b c y
Zi(S) 0 1 0 3 1 0 0 1 0 6 1 0 3 1 0 0 0

当我们要计算Z12(S)时,Z1(S)到Z11(S)都已经计算得到,此时的l=10,r=15,也就是说S[10...15]所形成的Z-block是当前的最右Z-block且包含S[12]。此时我们要计算Z12(S),由于S[10...15]=S[1...6],所以Z12(S)与Z3(S)密切相关,我们发现Z3(S)=0,3+Z3(S)=3<6,这个符合前面的第一种情况,所以Z12(S)=Z3(S)=0.

对于Z10(S),当计算Z10(S)时,已知的最右Z-block是S[8],l=8,r=8,因为10>8,所以符合上述第三种情况,我们直接从S[10]开始向后寻找S的包含前缀,找到S[10...15]是一个长度为6的S的包含前缀,所以Z10(S)=6,同时更新l=10,r=15.

在Zi(S)值计算中,第二种情况的场景比较少见,但是第二种情况也是Zi(S)计算中最容易出问题的部分。

下面给出我自己写的计算Z数组的算法

[cpp] view plain copy
  1. void ZBlock(const char* pattern, unsigned int length, unsigned int zvalues[])
  2. {
  3. unsigned int i, j, k;
  4. unsigned int l, r;
  5. l = r = 0;
  6. zvalues[0] = 0;
  7. for(i = 1; i < length; ++i)
  8. {
  9. if(i >= r)
  10. {
  11. j = 0;
  12. k = i;
  13. zvalues[i] = 0;
  14. while(k < length && pattern[j] == pattern[k])
  15. {
  16. ++j;
  17. ++k;
  18. }
  19. if(k != i)
  20. {
  21. l = i;
  22. r = k - 1;
  23. zvalues[i] = k - i;
  24. }
  25. }
  26. else
  27. {
  28. if(zvalues[i - l] >= r - i + 1)
  29. {
  30. j = r - i + 1;
  31. k = r + 1;
  32. while(k < length && pattern[j] == pattern[k])
  33. {
  34. ++j;
  35. ++k;
  36. }
  37. l = i;
  38. r = k - 1;
  39. zvalues[i] = k - i;
  40. }
  41. else
  42. {
  43. zvalues[i] = zvalues[i - l];
  44. }
  45. }
  46. }
  47. }

因为普通的字符串是从索引0开始,所以算法中对此作了调整。

Z-block算法从理论上彻底解决了前缀自包含的计算问题,从易理解的角度上讲,Z-block算法也要明显优于KMP算法中三人对next表构造过程的描述。拥有了模式串的Z值数组后,相应的KMP算法的next跳转表,BM算法的好后缀表的计算都将变得高效,直观。

再谈KMP/BM算法(I)相关推荐

  1. 【字符串算法1】 再谈字符串Hash(优雅的暴力)

    [字符串算法1] 字符串Hash(优雅的暴力) [字符串算法2]Manacher算法 [字符串算法3]KMP算法 这里将讲述  [字符串算法1] 字符串Hash 老版原文: RK哈希(Rabin_Ka ...

  2. 数据结构与算法--再谈递归与循环(斐波那契数列)

    再谈递归与循环 在某些算法中,可能需要重复计算相同的问题,通常我们可以选择用递归或者循环两种方法.递归是一个函数内部的调用这个函数自身.循环则是通过设置计算的初始值以及终止条件,在一个范围内重复运算. ...

  3. 经典算法研究系列:八、再谈启发式搜索算法

     经典算法研究系列:八.再谈启发式搜索算法 作者:July   二零一一年二月十日 本文参考: I.  维基百科. II. 人工智能-09 启发式搜索. III.本BLOG内,经典算法研究系列:一.A ...

  4. 【徒手写机器学习算法】再谈数据源:从普通图片到Cifar-10(使用C++)

    [徒手写机器学习算法]再谈数据源:从普通图片到Cifar-10(使用C++) 在本系列的第一篇文章里,关于机器学习的数据源的问题被一笔带过(使用csv格式的数据),这一篇文章我会给出关于图片数据制作的 ...

  5. 再谈GC1:GC简介,分代与回收算法

    说明:在本文中, Garbage Collection 翻译为 "垃圾收集", garbage collector 翻译为 "垃圾收集器";一般认为, 垃圾回收 ...

  6. 字符串匹配-BM算法改进SUNDAY--Boyer-Moore-Horspool-Sunday Aglorithm

    原文:http://blog.csdn.net/zhoubl668/article/details/7321271 BM算法的改进的算法SUNDAY--Boyer-Moore-Horspool-Sun ...

  7. 分享一下字符串匹配BM算法学习心得。

    字符串匹配BM(Boyer-Moore)算法学习心得 BM算法 是 Boyer-Moore算法 的缩写,是一种基于后缀比较的模式串匹配算法.BM算法在最坏情况下可以做到线性的,平均情况下是亚线性的(即 ...

  8. 字符串匹配算法(三):KMP(KnuthMorrisPratt)算法

    文章目录 KMP 原理 next数组的构建 代码实现 KMP 一提到字符串匹配算法,想必大家脑海中想到的第一个必然就是KMP算法,KMP算法的全称叫做KnuthMorrisPratt算法,与上一篇博客 ...

  9. 【数据结构与算法】字符串匹配 BM算法

    单模式串匹配 BF 算法和 RK 算法 BM 算法和 KMP 算法 多模式串匹配算法 Trie 树和 AC 自动机 BM算法 BM算法的核心思想是通过将模式串沿着主串大踏步的向后滑动,从而大大减少比较 ...

  10. diff算法阮一峰_【重学数据结构与算法(JS)】字符串匹配算法(三)——BM算法

    前言 文章的一开头,还是要强调下字符串匹配的思路 将模式串和主串进行比较 从前往后比较 从后往前比较 2. 匹配时,比较主串和模式串的下一个位置 3. 失配时, 在模式串中寻找一个合适的位置 如果找到 ...

最新文章

  1. 【Network Security!】虚拟化架构与系统部署
  2. 如何屏蔽PHP浏览器头信息X-Powered-By
  3. -又见GCD -- ACM解决方法
  4. 小白学jquery Mobile《构建跨平台APP:jQuery Mobile移动应用实战》连载四(场景切换)...
  5. ubuntu 安装PCL
  6. 【计算机科学基础】计算机不需要整数减法器的原因
  7. CCNET自动构建之路
  8. 金蝶云星空使用WebAPI来新增单据
  9. vue 添加子路由,并对路由重定向
  10. mt4双线macd_手机版MT4怎样添加双线MACD指标 手机MT4双线MACD设置方法
  11. VK Cup 2018 Round 1: A. Primal Sport
  12. matlab 韩明距离_科学网—Matlab中 pdist 函数详解(各种距离的生成) - 朱新宇的博文...
  13. WinRAR 5.60 官方简体中文无广告弹窗版本
  14. WORD插入多张图片并上下左右居中自动对齐操作;论文图片表格排版
  15. 那些年收藏的技术文章(二)-云笔记篇
  16. html怎么做出相框的效果,使用CSS3制作PS级的图片边框效果
  17. React Native 实践之携程 Moles 框架
  18. Linux安装mysql没有my.cnf文件
  19. 【文献解读 情感合成】Expressive Speech Driven Talking Avatar Synthesis with DBLSTM using 有限的情感双峰数据
  20. C# StatusBar

热门文章

  1. 【读书笔记《Bootstrap 实战》】6.单页营销网站
  2. 最短路径例题(Floyd、Dijkstra)
  3. vue+element+node构建单片机控制系统
  4. hql中 oracle当前时间,hql oracle 比较 日期时间
  5. mysql in range_mysql 的 RANGE 分区有价值吗?
  6. Linux下更改Python的软链接
  7. Python - PyCharm部分快捷键
  8. r语言集合补集_极速统计教程之八 | 概率和集合
  9. html是用来表示网上信息的符号标记语言,html标记的一般格式
  10. mongodb创建local库用户_MongoDB 在系统数据库local上无法创建用户的解决方法