基于位运算的最长公共子串算法

本文章来源

[常规动态规划算法]

L[i , j]等于A[1..i] , B[1..j]LCS.

则有L[i,j] = 1 + L[i-1 , j-1]                    如果 (A[i] = B[j])

              Max(L[i-1 , j] , L[i , j-1])   其他

复杂度为O(|A| * |B|)

 

[基于位运算的动态规划算法]

根据上面的动态规划算法,状态函数L具有如下性质:

L[i-1,j-1] ≤ L[i,j-1] , L[i-1,j] ≤ L[i,j]

  | L[i,j]-L[i-1,j-1] | ≤ 1

  对于L的每一行,相邻的两个元素的最多只相差1。这样一来,我们就可以用一个二进制的矩阵描述出L :    

#bits

 9    0 0 0 1 1 0 0 0 1 0 1 1 1 1 1 1 | T   string B

 9    0 1 0 0 1 0 0 0 1 0 1 1 1 1 1 1 | T 

 8    0 0 1 0 0 0 1 0 0 0 1 1 1 1 1 1 | C

 7    0 0 0 0 1 0 0 0 1 0 0 1 1 1 1 1 | T   - Row[11]

 7    1 0 0 0 0 0 0 1 0 0 0 1 1 1 1 1 | A   - Row[10]

 7    1 0 0 0 0 1 0 0 0 0 0 1 1 1 1 1 | G

 6    0 0 0 0 0 1 0 1 0 0 0 0 1 1 1 1 | A

 5    0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 1 | A

 5    0 0 0 0 1 0 0 0 0 0 0 0 1 1 1 1 | T

 4    0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 1 | T

 3    0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 1 | C

 3    1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 | G

 2    0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 | A

 1    0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 | T

     _________________________________.        matrix Mij

String A:  G T C T T A C A T C C G T T C G 

 

这里,我们将串A从右往左写,串B从下往上写。Row[i]中的1的个数总是和Row[i-1]中的1的个数一样多或者恰好多一个串A和串B的LCS即为最上面一行Row[|B|]中1的个数

 

字符比较串表

这里我们定义一组称为字符比较串的二进制串。分别是字符集中的每一个字符与串A的比较结果(相同为1,不同为0)

 

AG T C T T A C A T C C G T T C G

‘A’-string: 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0

‘C’-string: 0 0 1 0 0 0 1 0 0 1 1 0 0 0 1 0

‘G’-string: 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1

‘T’-string: 0 1 0 1 1 0 0 0 1 0 0 0 1 1 0 0

 

预先计算这个字符比较串表,时间复杂度为O(|S|*|A|)。对于一个确定的字符集,时间复杂度为O(|A|)。对于一个不确定的字符集,最坏情况为O(|A|*|B|)。如果字符集较小,|S|<<|B|,预处理的时间复杂度可以被忽略。

 

矩阵M

为了计算Row[i],我们需要用到字符比较串表中的B[i]-string。以Row[10]Row[11]为例,我们来研究如何计算出Row[i]

下面是Row[10] , 以及B[10]-string(‘T’-string). 按照Row[10]中的1分隔:

Row[10]: 1 0 0 0 0 0 0   1 0 0 0   1   1   1   1   1

T-string : 0 1 0 1 1 0 0   0 1 0 0   0   1   1   0   0

 

每一段都是Row[i-1]的一个1的位开始往右延伸,直到下一个位置是1或者串结束。如果Row[i-1]的最左边的位置上是0,那么最左边的一段从B[i]-string的最左边的1的位置开始延伸直到下一个1Row[i]的构成方式很简单:就是对于每一段,都是选择Row[i-1]或者B[i]-string最右边的1所在位置为1,其他的为0。如果这一段Row[i-1]和B[i]-string都是0,那么Row[i]这一段也为0

‘T’-string Or Row[10]:

  1 1 0 1 1*0 0    1 1*0 0   1*  1* 1*  1*  1*

Row[11]: 0 0 0 0 1 0 0    0 1 0 0   1   1   1   1   1

*表示了进行或操作之后每一段最右边的1

 

附带一提,你可以假定在每个串的最左边(位置|A|+1)存在一个1,这样可以方便处理最左边一段全为0的情况。不过对于本算法并没有这个必要。

Row[i-1]中的一个1的位置,代表了A中的一个最短前缀与B[1..i-1]LCS达到了该长度。引进B[i],最好的方法当然是在前面的最短基础上加入一个最短的与B[i]的匹配,也就是从那个1所在的位置往左找,找到的第一个能与B[i]匹配的(如果找得到的话)

X = Row[i-1] Or B[i]-string

对于当前的例子,X = Row[10] Or ‘T’-string

    X:  1 1 0 1 1 0 0    1 1 0 0   1   1   1   1   1

Row[i-1]往左移1位,并且设最低位为1,用X去减这个串:

 

X:         1 1 0 1 1 0 0    1 1 0 0   1   1   1   1   1

         - 0 0 0 0 0 0 1    0 0 0 1   1   1   1   1   1

           -------------    -------   -   -   -   -   -

           1 1 0 1 0 1 1    1 0 1 1   0   0   0   0   0

这个操作所表达的意思为对于每一段,把最右边的一个1变成0,把这个1右边的所有0变成1,设最低位为1的目的是为了可以同样处理最后一段

 

再跟X 进行Xor(异或)操作,得到:

0 0 0 0 1 1 1    0 1 1 1   1   1   1   1   1

这步操作之后的结果就是,对于每一段,从最右边的1开始到段尾变成1,其他变成0。因为Xor为1当且仅当两个位上的数不同,而上一次操作我们修改过的位是从最右边的1直到段尾

接下来就很显然了,只要再将上面的结果与X进行And操作即可。得到:

0 0 0 0 1 0 0    0 1 0 0   1   1   1   1   1

这样就得到了Row[i] , 即当前例子中的Row[11]

 

综上,

Row[i] = X And ( (X – ((Row[i-1] << 1) + 1)) Xor X),   

其中X = Row[i-1] Or B[i]-string

算法到此结束。计算出Row[|B|]之后,数一下其中有多少个1的位,便是答案了。

posted @ 2017-11-01 20:00  Iamhx  阅读(435)  评论(0编辑  收藏  举报