从自动机(NFA to DFA)角度去理解KMP和Shift-And/Shift-or算法

    晚上没事情翻起《柔性字符串匹配》(《Flexible Pattern Matching in Strings》)看,发现好多都忘记了,就从头开始从KMP看起来,个人感觉这本书上对KMP的描述比较晦涩,不像在网上搜索到的都是在说明模式函数 next那么好理解,但是这本书确是从本质上去描述KMP的——使用自动机的方法来进行模式串匹配,使得我们理解了这个本质,可以明白它不仅仅可以用来查 找字符串的。先简要说说KMP和shift-and/or算法吧。
    在模式串P的匹配中,在文本T的i位置上已经匹配了相同的模式串u( P1-Pu ),也就是说Ti-u+1 - Ti和u是匹配的

T:|*|*|*|*|*|...|i-u+1|i-u+2|...|i-1|i|i+1|*|*|...|*|*|*|*|*|*|
P:               |0    |1    |...|u-1|u|u+1|*|*|...
根据上图假设P0P1 = Pu-1Pu,设它为v
T:|*|*|*|*|*|...|i-u+1|i-u+2|...|i-1|i|i+1|*|*|...|*|*|*|*|*|*|
P:               |v          |...|v    |u+1|*|*|...
                                 |v    |B  |
假如Ti+1 = B,那么vB即是P的一个前缀也是Ti...Ti+1的一个后缀的最长字符串,此时称v是u的一个边界。换句话说边界v即是u的前缀也是u的后缀,并且B=Ti+1。
对于KMP的具体算法过程如下:
1. 对于模式串P的每个前缀u,预先计算(预先的意思就是不要管上面说的那个B)u的最长边界b(u)。
2. 设当前T的位置为i,u即是P的前缀,同时也是T1...Ti后缀的最长字符串。新读入的文本为Ti+1,那么按照下面的步骤计算新的最长前缀。
  2.1 如果Ti+1 = Pu+1,那么新的最长前缀是uPu+1,计算过程结束(这里是为了能在下一个字符不匹配的时候获取正确的b(u))。
  2.2 如果u = null,那么最新的最长前缀u是null,计算过程结束。否则u <- b(u),转到步骤1执行。
现在拿DNA序列看个例子AGATACGATATATAC中搜索ATATA
根据步骤1和边界定义我们可以知道
u
b(u)
Anull
AT
null
ATA
A
ATAT
AT
ATATA
ATA
1.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
  |A|T|A|T|A
  根据2.2计算过程结束,那么从下一个字符重新开始
2.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
    |A|T|A|T|A
  根据2.2计算过程结束,那么从下一个字符重新开始
3.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
      |A|T|A|T|A
  在到P3的时候根据2.1可以知道现在最长前缀u为ATA由于P4和C不匹配,那么根据2.2中,直接将P和T按照最大边界向右对齐
4.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
          |A|T|A|T|A
  此时u为A,根据2.2,又因为b(u)=null那么从下一个字符重新开始
5.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
            |A|T|A|T|A
  同2
6.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
              |A|T|A|T|A
  同5
7.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
                |A|T|A|T|A
  得到一个匹配,并且当且u为ATATA,按照b(u)对齐
8.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
                    |A|T|A|T|A
  再次得到一个匹配,再次按照b(u)对起
9.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
                        |A|T|A|T|A
  u = ATA,对齐
10.|A|G|A|T|A|C|G|A|T|A|T|A|T|A|C|
                             |A|T|A|T|A
下面两次就不再说了。

    现在说说shift_and和or算法
    其实两个思想一样就是采用掩码并利用位并行来快速匹配模式串。
    它维护一个集合,集合中的每个字符串即是P的前缀也是已读入文本的后缀,每读入一个新文本字符串,便用位并行来更新该集合。该集合用一个掩码表示D=dm...d1表示。
    D的第j位是有效的,当且仅当P1...Pj是T1...Ti的后缀。
    每当读入Ti+1的时候需要重新计算掩码D'。D'的j+1位有效当且仅当D的第j位是有效的并且Ti+1与Pj+1相等。
    首先根据建立一个表B,记录字母表中的每个字符的掩码bm...b1。加入Pj=c,那么B[c]的第j位为1,否则为0。首先D=0^m,可用如下公式更新。
    D' = ( ( D << 1 ) | 0^(m-1)1 ) & B[t+1](这里的0^(m-1)不是幂运算,是说前面有m-1个0)
    这里要和0^(m-1)1或,是因为,空字符串null也是文本后缀。
    shift-or是通过对shift-and公式中进行取反去掉掩码0^(m-1),用0来表示,Pj=c B[c]第j位为0,否则为1。
    D' = ( D << 1 ) | B[t+1]
    当D'最高位有效时,那么可以确定一个完整的匹配出现了。

    该说说自动机了,看到shift算法的公式,很明显的就是状态转移函数,当输入匹配的时候,就根据相应的转移函数进行状态转换,否则回到起始状态。多么明显的非确定有限自动机(NFA)啊。(由于csdn上传图片不了,也没法画图看了,大家可以想象大概的样子一个起始状态X无效输入仍转为自己,当有有效输入A的时候转入1,接着输入T转到2,以此到最后一个有效输入A转到5,状态1-4无效输入仍转到X,5之后的任何输入都会转到X)
    应该知道NFA都会有一个与其对应的DFA存在,那么既然这里都提到了KMP和shift,那么KMP就是一个DFA的模型了。
    从NFA到DFA的转化是将NFA的状态集合对应到DFA的一个状态。求u的b(u)的过程,也就是将NFA转换为DFA的过程。想想就可以知道为什么了,NFA到DFA转换关键过程中如下
    1.假定I是NFA的状态集的子集,定义
I的闭包为ε-closure(I)
     1.1 若q 属于 I,那么q属于
ε-closure(I)
     1.2 若q 属于 I,那么从q出发经任意条
ε(任意输入)弧 能到达的状态 q' 都属于 ε-closure(I)
   2.假定I是NFA的状态集的子集,a属于字母表,定义Ia =
ε-closure(J)
     其中J是那些从I出发经过一条a弧而到达的状态点的全体。
   将这些闭包体通过通过所有类似a弧的相连就可以得到DFA,记得上面说KMP中计算前缀u的边界,那就是求闭包,那个B和这个a弧是等价的。

   写了这么多,没有说明算法本身的特点,只是阐明了我对这两个算法之间关系的理解和它们本质的理解,有错误欢迎朋友们拍砖啊。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值