算法导论之动态规划(最长公共子序列和最优二叉查找树)

动态规划师通过组合子问题的解而解决整个问题,将问题划分成子问题,递归地求解各子问题,然后合并子问题的解而得到原问题的解。和分治算法思想一致,不同的是分治算法适合独立的子问题,而对于非独立的子问题,即各子问题中包含公共的子子问题,若采用分治法会重复求解,动态规划将子问题结果保存在一张表中,避免重复子问题重复求解。

动态规划在多值中选择一个最优解,其算法设计一般分为4个步骤:描述最优解的结构;递归定义最优解的值;按自底向上的方式计算最优解的值;由计算出的结果构造一个最优解。

1)装配线调度

求解最快通过工厂装配线路线的问题,问题描述:有两条装配线,用于安装汽车底盘的零件,建造时间不同采用技术有差异导致安装时间有快慢;每条装配线有n个装配站,装配线i的第j个装配站表示为Sij,相应装配时间为aij,每条装配线上一样编号的装配站执行相同的功能,如S1j和S2j

三个时间点说明:

进入时间:一个汽车地盘进入装配线(i为1或2),花费时间为ei

移动时间:在同一条装配线上的相邻装配站(从j到j+1)移动没有时间开销,但如果从一条装配线的装配站Sij移动到另一条装配线上,移动需花费时间tij

离开时间:在完成一条装配线n个装配站后,花费xi时间离开装配线。

要求解的问题是:求解分别通过装配线1的几个站和装配线2的几个站,才能使汽车底盘零件完成安装的时间最小。

如果已知一个序列的,在装配线1使用那些站,在装配线2使用那些站,则可以在线性时间内得出一个底盘通过工厂装配线要花的时间。但要知道那条路线是时间花费最小,却有2n中可能,一条装配线所使用的装配站可以看成是{1,2,…,n}的子集。动态规划法就是从2n中可能发现最优解,我们按照动态规划法四个步骤来说明:

第一步骤:描述最优解的结构

这一步其实就是找到最优解的特征,从而能够将问题转化为若干个子问题来求解。对于装配线调度花费时间最小问题,要描述其最优解,要从一个最基本的假设出发。假设通过装配站S1j的最快路线通过装配站S1j-1,那么底盘是利用了最快的路线从开始点到装配站S1j-1,如果不是,就是存在另一个条从开始点到装配站S1j-1的更快路线,这就形成矛盾。这个思路理解起来很简单,就是假设你是最快的途经点,那么该点前面的路线也一定是最快,否则就不会经过该店。

照此,找出通过装配站S1j的最快路线的问题最优解,可以转化为寻找S1j-1或S2j-1最快路线的子问题的最优解。这个就是:要寻找该点的最快,那么就找出该点前面必经的点的最快,这里装配线只有两个点会是子结构,装配线1的j-1站或装配线2的j-1站。这和路线规划一致,要找到某一点最短路径,只要找出这一点前面所有点的最短路径即可,把问题拆借成子问题。

总结下,装配线最快时间调度问题的最优解结构:

通过装配站S1j的最快路线有两个可能:一是通过装配站S1j-1的最快路线,然后直接通过装配站S1j;二是通过装配站S2j-1的最快路线,从装配线2移动到转配线1,然后通过装配站S1j;类似,装配站S2j的最快路线也有两个可能。

第二步骤:递归定义最优解的值

输入:装配站执行时间aij,装配线之间装配站移动时间tij,装配线进入时间ei和装配线退出时间xi及每条装配线装配站的数量n

Fun_FastestWay(a,t,e,x,n){

f1[1]=e1+a11

f2[1]=e2+a21

for j=2 to n

do if f1[j-1]+ a1j=<f2[j-1]+ a1j +t2j-1

then f1[j]=f1[j-1]+a1j

l1[j]=1 //装配线

else f1[j]= f2[j-1]+a1j +t2j-1

l1[j]=2

if f2[j-1]+ a2j=<f1[j-1]+ a2j +t1j-1

then f2[j]=f2[j-1]+a2j

l2[j]=2

else f2[j]= f1[j-1]+a2j +t1j-1

l2[j]=1

if f1[n]+ x1 =< f2[n]+x2

then f= f1[n]+ x1

                    l=1

else f= f2[n]+ x2

                    l=2

}

这个算法过程就是计算出每一个装配站的值,记录在表格中并保存。

第四步骤:构造最优解

上个步骤计算出的每个装配站时间,就可以输出最快路线的最优解。

2)矩阵链乘法

提出用动态规划法解决矩阵链乘法,有两个前提,一个是矩阵乘法满足结合律;另一个是当两个矩阵相容时才能相乘,所谓相容,就是A的列数等于B的行数。对于n个矩阵链相乘,用括号分组求解,然而不同的加括号分组顺序,在求积的时间上有很大不同。矩阵A(pxq)和B(qxr)相乘,运算次数为pxqxr次,一个矩阵组的不同分组顺序显然在时间性能上会有很大不同。

这就提出了矩阵链乘法的问题:由n个矩阵构成的一个链<A1,A2,…,Ai,…,An>,其中,i=1,2,…,n,矩阵Ai的维数(行X列)为pi-1xqi,以最小化的运算次数对矩阵链进行括号分组,求得A1A2…An乘积。针对问题,采用动态规划法四个步骤来求解。

第一步:描述最优解的结构

动态规划方法的第一步就是寻找最优子结构,利用子结构,可以根据子问题的最优解构造出原问题的一个最优解。一个问题的最优解可以由子问题的最优解构成,而这种最优子结构显然是可以构成递归的。对于矩阵链乘法的最优子结构也很好理解,Ai,j表示矩阵乘积AiAi+1…Aj求值的结果,其中i<=j,取k为i和j之间的取值的,可以将矩阵Ai,j的求值分成AiAi+1…Ak到AkAk+1…Aj两个区间并求其最优解,类似装配线调度问题,假设AiAi+1…Aj为最优解,则从k分开的两个区间是最优加括号。

有了这样的最优子结构,就可以根据子问题的最优解来构造原问题的一个最优解。矩阵链乘法求解的问题,可以构造出最优子结构,或者说存在子结构可以求得最优解,即分割乘积,问题的最优解包含了子问题的最优解。

第二步:定义递归最优解

这一步就是根据子问题的最优解来递归定义一个最优解的代码。矩阵链乘法的问题就是确定AiAi+1…Aj加全部括号的乘积最小代价问题。设m[i,j]为计算矩阵Ai,j所需的标量乘法运算次数的最小值,就是给矩阵链怎么分割(用加括号)才能让乘积运算次数代价最小。那么,对整个问题来说,计算A1,n的最小代价就是m[1,n]。

假设最优加全部括号将乘积AiAi+1…Aj从Ak和Ak+1之间分开,其中i=<k<j,使m[i,j]为计算子乘积AiAi+1…Ak和AkAk+1…Aj的代价,再加上两个矩阵相乘的代价,假设每个矩阵Ai从是pi-1xpi的,则Ai…kAk+1…j要进行pi-1pkpj次标量乘法,从而得出:

第三步:计算最优代价

同样的,如果用递归来实现,算法是指数级时间,而矩阵链乘法问题的子问题显然满足重叠这个性质,可以用自底向上的表格法来计算最优代价。适合动态规划法的问题,第一个特点就是具有最优子结构,第二个特点就是子问题重叠。

假设矩阵Ai的维数是pi-1xpi,i=1,2,…,n。输入一个序列p=<p0,p1,…,pn>,length[p]=n+1,设计一个辅助表m[1…n,1…n]来保存m[i,j]的代价,并使用辅助表s[1…n,1…n]来记录m[i,j]取得最优解时k的值。

Fun_Matrix_chain_order(p){

n= length[p]-1;

for i=1 to n

do m[i,j]=0;//i=j下代价为0

for l=2 to n

do for i=1 to n-l+1

do j=i+l-1;

m[i,j]=∞;//初始化i<j的每一个区间代价

for k=i to j-1

do q=m[i,k]+m[k+1,j]+ pi-1pkpj

                        if q<m[I,j]

then m[i,j]=q;

s[i,j]=k;

return m and s;

}

这个算法比较好理解,就是循环计算每个区间的代价,然后用两个辅助表记录,三层循环,算法时间为O(n3),空间需要两个表的耗费。

第四步:构造最优解

有了第三步的矩阵链乘积最优标量乘法次数,利用m和s表很容易构造出最优解。

3)动态规划的特性

动态规划方法的四步骤通过矩阵链乘法和装配线调度两个问题已经比较清晰勾勒了,需要总结下现实中怎样的问题适合用动态规划法来解决呢?

前文已经提到,适合采用动态规划方法来求解最优问题需要满足两个特性:具有最优子结构和重叠子问题,同时子问题是独立的。

如果问题的一个最优解中包含了子问题的最优解,则该问题具有最优子结构。寻找最优子结构,可以遵循如下模式:

问题的一个解是一个选择,对于给定的问题存在最优解的选择,且子问题的最优解具有相同结构,可以由子子问题的最优解构成。

通俗地说:这个问题可以分成具有相同结构的子问题,这样就可以把求问题最优解分到求子问题的最优解。相同结构也将是说问题是重叠的,可以将解原问题的递归算法反复地用来解子问题。这个分治法解决的问题不一样,分治法产生的子问题都是全新的,不具有相同结构。换句话说,动态规划法要解决的问题,具有俄罗斯套娃一样的性质,而分治法面对的则是子问题都具有不同结构和求解性质,不能将同样算法用于问题和不同子问题。这里还需要提到的是贪心算法和动态规划法一样适用于问题具有最优子结构,不同的是,贪心算法是以自顶向下的方式来适用最优子结构,而动态规划师自底向上;就是说,贪心算法和动态规划法面对最优子结构,贪心算法是先定最优解再求子问题最优解,而动态规划正好相反,先求子问题最优解在推问题最优解;贪心算法是先选择最优而后求解,动态规划法是先求解最优而后选择。

值得说明的是,动态规划要求其子问题重叠同时也要求其独立。何谓子问题独立?就是同一个问题的两个子问题不共享资源,则他们是独立。而重叠是指子问题是相同的,只不过作为不同问题的子问题。对于理解子问题是独立的,导论中给出了无权最短路径和无权最长简单路径的分析,回到现实中,也是很好理解这个案例所说明的全权最长简单路径的子问题不独立,无法应用动态规划法来解决。最短路径就不说了,最长问题分解到子问题,显然在某个图节点上不是独立的。

更有现实应用意义的当属应用动态规划法寻找最长公共子序列和最优二叉查找树。最大公共子序列(LCS)文中提到可用于DNA串相似匹配,就是寻找两个字符串之间具有公共序列的最长度。最优二叉查找树文中提到用于单词翻译,并根据频率构造最优二叉树,这显然也适合于根据频率来排序的场景。这两个案例的动态规划法构造四步骤过程就不描述,原理类似,最主要在现实应用中,寻找满足动态规划法解决的问题,并改良以适用。

后续如遇到类似最长公共子序列和最优二叉查找树的场景,再研究其动态规划法来解决。

时间: 2025-01-12 23:23:22

算法导论之动态规划(最长公共子序列和最优二叉查找树)的相关文章

《算法导论》读书笔记之动态规划—最长公共子序列 &amp; 最长公共子串(LCS)

From:http://my.oschina.net/leejun2005/blog/117167 1.先科普下最长公共子序列 & 最长公共子串的区别: 找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的.而最长公共子序列则并不要求连续. 2.最长公共子串 其实这是一个序贯决策问题,可以用动态规划来求解.我们采用一个二维矩阵来记录中间的结果.这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是

算法系列笔记6(动态规划—最长公共子序列/串lcs)

子序列要求元素顺序一致就可以了,而字串必须是连续的.如ABCBDAB与BDCABA两个字符串,最长公共子序列有BCBA.BDAB和BCAB, 而最长公共字串只有AB和BD<连续>.当然这里的求解只求一个,但通常是这样直接说求最长公共子串,子序列,准确的应该是之一. 最长公共子序列 法一:穷举法 检查字符串x所有字序列,共有2^m个,检查它是否在y字符串中出现,每个需要O(n),时间复杂度为指数级的. 法二:动态规划(DP) 将两个字符串x[1-m]和y[1-n]放在x轴和y轴方向上便得到一个二

算法 动态规划 ------最长公共子序列

这篇博客是我 听完这位老师讲完课的 记录.https://www.youtube.com/watch?v=mgDUoITB24I&t=645s    还有我个人的理解,如果有错误或者有争议的地方,欢迎留言.谢谢大家~ 子序列  就是说 从原有列表中按照出现先后顺序从中选择部分 元素 组成的新的列表. 最长公共子序列的含义:  选择两个列表中公共的子序列的最大长度,这个子序列即为最长公共子序列. 用蛮力的方法求解,对于长度为 M的 列表,它所有的子序列的个数有  2^m  ,每个元素有两种情况,被

动态规划 - 最长公共子序列(LCS)

最长公共子序列也是动态规划中的一个经典问题. 有两个字符串 S1 和 S2,求一个最长公共子串,即求字符串 S3,它同时为 S1 和 S2 的子串,且要求它的长度最长,并确定这个长度.这个问题被我们称为 最长公共子序列问题. 与求最长递增子序列一样,我们首先将原问题分割成一些子问题,我们用 dp[i][j]表示 S1 中前 i 个字符与 S2 中前 j 个字符分别组成的两个前缀字符串的最 长公共子串长度. 显然的,当 i. j 较小时我们可以直接得出答案,如 dp[0][j]必 等于 0.那么,

动态规划3-最长公共子序列问题

一些概念: (1)子序列: 一个序列A = a1,a2,……an,中任意删除若干项,剩余的序列叫做A的一个子序列.也可以认为是从序列A按原顺序保留任意若干项得到的序列. 例如: 对序列 1,3,5,4,2,6,8,7来说,序列3,4,8,7 是它的一个子序列.对于一个长度为n的序列,它一共有2^n 个子序列,有(2^n – 1)个非空子序列. 请注意:子序列不是子集,它和原始序列的元素顺序是相关的. (2)公共子序列 : 顾名思义,如果序列C既是序列A的子序列,同时也是序列B的子序列,则称它为序

动态规划-最长公共子序列

(1).问题描述:给出2个序列,x是从1到m,y是从1到n,找出x和y的最长公共子序列? x:A B C B D A B y:B D C A B A 则:最长公共子序列长度为4,BDAB BCAB BCBA均为LCS(最长公共子序列): 模型实现图: (2).问题解决 代码实现了最长公共子序列的长度 #include<stdio.h> #define N    10 int LCS(int *a, int count1, int *b, int count2); int LCS(int *a,

动态规划——最长公共子序列

最长公共子序列(LCS)是一类典型的动归问题. 问题 给定两个序列(整数序列或者字符串)A和B,序列的子序列定义为从序列中按照索引单调增加的顺序取出若干个元素得到的新的序列,比如从序列A中取出 A[i1], A[i2], ...A[ik],其中0=< i1 <= i2 <= ... ik <= n-1得到的新的序列 A[i1].A[i2]....A[ik]即为A的一个子序列.     两个不同的原序列A和B可能有着相同的子序列,求出A和B的公共子序列的最长长度. 分析     这种

第十五章 动态规划——最长公共子序列

1.基本概念 一个给定序列的子序列就是该给定序列中去掉零个或者多个元素的序列.形式化来讲就是:给定一个序列X={x1,x2,……,xm},另外一个序列Z={z1.z2.……,zk},如果存在X的一个严格递增小标序列<i1,i2……,ik>,使得对所有j=1,2,……k,有xij = zj,则Z是X的子序列.例如:Z={B,C,D,B}是X={A,B,C,B,D,A,B}的一个子序列,相应的小标为<2,3,5,7>.从定义可以看出子序列直接的元素不一定是相邻的. 公共子序列:给定两个

动态规划——最长公共子序列(LCS)

最长公共子序列的问题描述为: 下面介绍动态规划的做法. 令 dp[i][j] 表示字符串 A 的 i 号位与字符串 B 的 j 号位之前的 LCS 长度(下标从 1 开始),如 dp[4][5] 表示 "sads" 与 "admin" 的 LCS 长度.那么可以根据 A[i] 和 B[j] 的情况,分为两种决策: 若 A[i]==B[j],则字符串 A 与字符串 B 的 LCS 增加了 1 位,即有 dp[i][j]=dp[i-1][j-1]+1. 若 A[i] !