动态规划-序列联配问题(2)以算代存

引言

接着上篇,对于两个字符串 S m , T n S_m,T_n Sm,Tn ,我们计算其最优对齐,需要的数组空间是O(mn)。最优对齐的方法在处理短字符串的时候内存还可以承受,但是当处理输入变成一整篇论文这样的东西时,内存就受不了了。另一方面,由于算法采用2层for循环计算二维数组m x n,但最后我们很可能用不到那些多余的内容(数组的边角部分),这就产生了浪费。

高级动态规划

动态规划对于最优解的获取有着十分显著的效果,但是其占用大量的存储空间和许多不必要的计算。而高级动态规划弥补了这一缺陷,其节省了存储空间和运行时间。采用方法是以算代存。

分治减少空间

我们在之前的对齐算法里,可以发现每次计算当前格子的分值,考虑的是其左上角三个格子的内容,其余格子都不做考虑,实际上我们每次计算并不需要完整的二维数组的所有数据,只需下如下两列的数据即可:
在这里插入图片描述
一开始,我们初始化第0列(耗费一个数组),当计算第1列的时候,第一个元素-3可以直接初始化得到,对于第一个蓝色框,左上角三个值都已得到,直接根据递推表达式,得到1,在此基础上进行第二个蓝色框框的计算,以此类推,计算得到前2列的内容。

而在计算第2列的内容时,第0列的内容已经没必要保存了,因此将第1列的数值保存到前一个数组中,进行下一轮的计算,如下图:
在这里插入图片描述
最终我们数组保存的内容只有最后两列,自然就得到了最后的答案4。

伪代码如下:
在这里插入图片描述
可以看到这个方法我们只需要2n空间,但是如果我们想要回溯路径就不行了,因为之前的计算结果没有保存。
在这里插入图片描述
Hirschberg的算法加入了分治,解决了这一问题。
我们可以发现以算代存,从前往后匹配和从后往前匹配都是同样的逻辑。
Hirschberg将分治思想应用到了动态规划当中。从多步决策的角度思考,S如何从T一步步产生?应用分治的思想,将S分成两部分,前半部分从T的前一部分产生,后半部分从T的后一部分产生,得到公式如下:
O P T ( T , S ) = O P T ( T [ 1.. q ] , S [ 1.. n 2 ] ) + O P T ( T [ q + 1.. m ] , S [ n 2 + 1.. n ] ) OPT(T,S) = OPT(T[1..q],S[1..\frac{n}{2}])+OPT(T[q+1..m],S[\frac{n}{2}+1..n]) OPT(T,S)=OPT(T[1..q],S[1..2n])+OPT(T[q+1..m],S[2n+1..n])
应用了分治思想,将S分成两部分,那么我们就可以分别对前半部分采用后缀匹配,后半部分采用前缀匹配,并且只用两个数组存储中间结果,如下:
在这里插入图片描述
我们将这两列的结果加起来,得到中间黄色列的结果,发现中间得到了之前计算的最优分值4!它是由1+3得到的。1是“OCUR”和“OCCUR“相似度,3是”RANCE“和”RENCE“相似度,由于我们采用线性加和得分,1+3的结果就是S与T的相似度!等一下,那这个4出现的位置有什么意义呢?看下面这张图:
在这里插入图片描述
我们发现4将左右分成了上下两部分,而这里的R正好就是q的位置。我们由此可以得到原公式第一次分治如下:
O P T ( ′ O C C U R R E N C E ′ , ′ O C U R R A N C E ′ ) = O P T ( ′ O C C U R ′ , ′ O C U R ′ ) + O P T ( ′ R E N C E ′ , ′ R A N C E ′ ) OPT('OCCURRENCE','OCURRANCE') = OPT('OCCUR','OCUR') +\\ OPT('RENCE','RANCE') OPT(OCCURRENCE,OCURRANCE)=OPT(OCCUR,OCUR)+OPT(RENCE,RANCE)
剩下的就是对左上角红色区域和右下角红色区域进行递归调用,我们就可以得到一系列像之前4一样的红色格子。

那么,该如何得到想要的路径呢?要想到路径,首先要真正理解q的意义,q表明了S的前一半是从T的[1…q]得到的,体现在例子中,就是"OCUR"是从“OCCUR”来的。回到之前的路径表上:
在这里插入图片描述
我们从图中容易知道,每一个行至少有一个元素在路径中,而q确定了这个元素所处的列!第一次分治的q确定了中间黄色的1的位置,q的值是5,也即告诉我们原二维数组<5,4>一定在路径中,4是一开始S一分为二确定的。同样的,每次递归我们都能确定新的在路径中的元素。这样我们就可以慢慢确定路径了。

伪代码如下:
在这里插入图片描述
上图算法总的空间耗费是O(m+n),为线性存储空间;时间复杂度也是O(mn)的。
在这里插入图片描述

分治点改进

上面的算法固然很好,但也存在缺点,伪代码的2、3两行,这里要求问题必须可以从前往后计算,也能从后往前计算,在对齐问题中可以这么做,但如果换成其他问题就不一定了。

要获得路径,根据上文的推导,实际上只要将q递归地计算出来即可,定义一个变量 R i , j R_{i,j} Ri,j表示单元(i,j)回溯到(0,0)在哪一行经过n/2 ,我们有如下公式:
在这里插入图片描述
举个例子说明这个递推公式的意思,首先,对于刚好在n/2 列的元素,其通过n/2列时的行必然是其本身行,而对于后面列的元素,就需要考虑其OPT的计算结果了。
在这里插入图片描述
在递归计算的时候,我们总是有两个数组(一绿,一蓝)用于保存当前计算的最优对齐,对于n/2 +1列的元素,它想知道自己回溯的时候在哪一行通过n/2 列,它需要向前询问。询问的基础在于它是从哪里走过来的,根据OPT的分值对比,我们可以清楚知道蓝色框中的5是从绿色框中的3得来的,同样我们不断向后更新,可以得到最后一列结果为5,这说明了q= R 10 , 9 R_{10,9} R10,9,我们用这个方法实现了q的查找,剩下就是递归的过程了。下面是递归计算的一个例子:
在这里插入图片描述
可以递归调用是计算“OCUR”,“OCCUR”之间的q,当计算“OC”,“OCC”的时候q的值有两个,这也很好理解,“O-C”可以与“OCC”对齐,“OC-”也可以与“OCC”对齐,因此q有两个选项。
在这里插入图片描述
下面是整个递归过程存储的路径点过程
在这里插入图片描述
那么我们就有路径点集A={⟨5,4⟩,⟨3,2⟩,⟨2,1⟩,⟨4,3⟩,⟨7,6⟩,⟨6,5⟩,⟨8,7⟩,⟨9,8⟩}

总结

在动态规划中应用分治思想,其能够降低空间复杂度的核心是以算代存。其大致步骤如下:

  1. 将原本S如何从T中产生的多步决策问题,转换成了S的前一半如何从T的前一部分产生(T[1…q],S[1…n/2]+S的后一半如何从T的后一部分产生(T[q+1…m],S[n/2+1…n])
  2. 接下来我们对前后两部分分别采用后缀最优对齐,前缀最优对齐,最后计算得到两列结果,将两列结果求和,取最大者(就是之前的4)的横坐标就是q
  3. 有了q,我们就能存储⟨q,n/2⟩(路径必经之点)到数组中,然后递归1-2步过程,最终得到的数组A构成了我们需要的路径。

但上述方法有一定限制,它要求问题必须同时满足前缀最优对齐和后缀最优对齐,因此又提出了lan的算法分治递归,本质上要找到路径就是找到q这个值,因此他考虑利用OPT递归计算q,同样得到点集A。

猜你喜欢

转载自blog.csdn.net/qq_32505207/article/details/108043063
今日推荐