动态规划是一种设计技巧,而不是一种特定的算法,就像分治法一样。
有两个序列,序列x[1~m],序列y[1~n],找到它们的最长公共子序列,子序列不需要在原序列中占用连续的位置(最长公共子串要求连续),子序列可能不唯一。
检查x[1~m]的每个子序列在y[1~n]中也是否存在,找到其中最长的。
运行时间分析:
检查是否存在代价为O(n)
x中的子序列数量为2^m,可以用一个m位的向量来表示,1表示选取那个字符,0表示不选取那个字符。
总运行时间为O(n·2^m),指数级的运行时间是非常慢的。
1.计算最长公共子序列的长度(唯一)。
2.扩展算法使其能够找到最长公共子序列。
注:|S|表示序列S的长度。
x[i]=y[j]的情况:
设z[1~k]=LCS(x[1~i],y[1~j]),且有c[i,j]=k,则有z[k]=x[i](=y[j])。
证明z[1~k-1]=LCS(x[1~i-1],y[1~j-1]):
假设存在w为x[1~i-1],y[1~j-1]的一个子序列且|w|>k-1。
w和z[k]合成的序列(剪贴法),也应是x[1~i],y[1~j]的一个子序列。
|w和z[k]合成的序列|>k。
与c[i,j]=k矛盾,假设不成立,z[1~k]=LCS(x[1~i],y[1~j])得证。
因此有c[i-1,j-1]=k-1,c[i,j]= c[i-1,j-1]+1。
x[i]≠y[j]的情况:
可利用与上面方法类似的方法证明。
LCS(x,y,i,j)//忽略基本情况
if x[i]=y[j]
c[i,j] ← LCS(x,y,i-1,j-1)+1
else
c[i,j] ← max{LCS(x,y,i-1,j), LCS(x,y,i,j-1)}
return c[i,j]
LCS(x,y,i,j) //忽略基本情况
if c[i,j]=nil
if x[i]=y[j]
c[i,j] ← LCS(x,y,i-1,j-1)+1
else
c[i,j] ← max{LCS(x,y,i-1,j), LCS(x,y,i,j-1)}
return c[i,j]
自底向上的计算c[i,j]表。
Ex:
x:A B C B D A B
y:B D C A B A
右下角即为最长公共子序列的长度,运行时间为Θ(m·n),且利用了内存访问的局部性特点,此算法效率很高。
回溯法重建LCS:
BCBA即为一个最长公共子序列,走别的路径即可得到其他几个结果。
朴素算法占用空间为Θ(m·n),改进算法只使用两行或两列可将占用空间减小到O(min{m,n})。
思考:改进算法如何重建LCS?(提示:采用分治法)