动态规划_Dynamic Programming

来源:互联网 发布:知乎 男士服装 编辑:程序博客网 时间:2024/05/20 18:18
动态规划  Dynamic Programming (转)
提要    本文介绍了动态规划的基本思想和基本步骤,通过实例研究了利用动态规划设计算法的具体途径,讨论了动态规划的一些实现技巧,并将动态规划和其他一些算法作了比较,最后还简单介绍了动态规划的数学理论基础和当前最新的研究成果。
目录
引言
动态规划的基本概念
动态规划的基本定理和基本方程
动态规划的适用条件
动态规划的基本思想
动态规划的基本步骤
动态规划的实例分析
动态规划的技巧
动态规划实现中的问题
动态规划与其他算法的比较
动态规划的理论基础
其他资料
function MinDistance(v):integer;
begin
 if v=E then return 0
  else
   begin
    min:=maxint;
    for 所有没有访问过的节点i do
     if vi相邻 then
      begin
        标记i访问过了;
        t:=vi的距离+MinDistance(i);
        标记i未访问过;       
        if t 
      end; 
   end;
end;
 
开始时标记所有的顶点未访问过,MinDistance(A)就是从A到E的最短距离。
这个程序的效率如何呢?我们可以看到,每次除了已经访问过的城市外,其他城市都要访问,所以时间复杂度为O(n!),这是一个“指数级”的算法,那么,还有没有更好的算法呢?
首先,我们来观察一下这个算法。在求从B1到E的最短距离的时候,先求出从C2到E的最短距离;而在求从B2到E的最短距离的时候,又求了一遍从C2到E的最短距离。也就是说,从C2到E的最短距离我们求了两遍。同样可以发现,在求从C1、C2到E的最短距离的过程中,从D1到E的最短距离也被求了两遍。而在整个程序中,从D1到E的最短距离被求了四遍。如果在求解的过程中,同时将求得的最短距离"记录在案",随时调用,就可以避免这种情况。于是,可以改进该算法,将每次求出的从v到E的最短距离记录下来,在算法中递归地求MinDistance(v)时先检查以前是否已经求过了MinDistance(v),如果求过了则不用重新求一遍,只要查找以前的记录就可以了。这样,由于所有的点有n个,因此不同的状态数目有n个,该算法的数量级为O(n)。
更进一步,可以将这种递归改为递推,这样可以减少递归调用的开销。
请看图1,可以发现,A只和Bi相邻,Bi只和Ci相邻,...,依此类推。这样,我们可以将原问题的解决过程划分为4个阶段,设
显然可以递推地求出F1(A),也就是从A到E的最短距离。这种算法的复杂度为O(n),因为所有的状态总数(节点总数)为n,对每个状态都只要遍历一次,而且程序很简洁。
具体算法如下:
procedure DynamicProgramming;
 begin
  F5[E]:=0;
  for i:=4 downto 1 do
     for each u ∈Sk do
      begin
       Fk[u]:=无穷大;
       for each v∈Sk+1∩δ(u) do
         if Fk[u]>w(u,v)+Fk+1[v] then Fk[u]:=w(u,v)+Fk+1[v];
   end;
  输出F1[A];
 end;
这种高效算法,就是动态规划算法
二、动态规划的基本概念
1、动态规划的发展及研究内容
动态规划(dynamic programming)是运筹学的一个分支,是求解决策过程(decision process)最优化的数学方法。20世纪50年代初美国数学家R.E.Bellman等人在研究多阶段决策过程(multistep decision process)的优化问题时,提出了著名的最优化原理(principle of optimality),把多阶段过程转化为一系列单阶段问题,逐个求解,创立了解决这类过程优化问题的新方法——动态规划。1957年出版了他的名著Dynamic Programming,这是该领域的第一本著作。
动态规划问世以来,在经济管理、生产调度、工程技术和最优控制等方面得到了广泛的应用。例如最短路线、库存管理、资源分配、设备更新、排序、装载等问题,用动态规划方法比用其它方法求解更为方便。
虽然动态规划主要用于求解以时间划分阶段的动态过程的优化问题,但是一些与时间无关的静态规划(如线性规划、非线性规划),只要人为地引进时间因素,把它视为多阶段决策过程,也可以用动态规划方法方便地求解。
2、多阶段决策过程与多阶段决策问题
多阶段决策过程,是指这样的一类特殊的活动过程,问题可以按时间顺序分解成若干相互联系的阶段,在每一个阶段都要做出决策,全部过程的决策是一个决策序列。要使整个活动的总体效果达到最优的问题,称为多阶段决策问题
例1是一个多阶段决策问题的例子,下面是另一个多阶段决策问题的例子:
[2] 生产计划问题    工厂生产某种产品,每单位(千件)的成本为1(千元),每次开工的固定成本为3(千元),工厂每季度的最大生产能力为6(千件)。经调查,市场对该产品的需求量第一、二、三、四季度分别为 2,3,2,4(千件)。如果工厂在第一、二季度将全年的需求都生产出来,自然可以降低成本(少付固定成本费),但是对于第三、四季度才能上市的产品需付存储费,每季每千件的存储费为0.5(千元)。还规定年初和年末这种产品均无库存。试制订一个生产计划,即安排每个季度的产量,使一年的总费用(生产成本和存储费)最少。
3、决策过程的分类
根据过程的时间变量是离散的还是连续的,分为离散时间决策过程(discrete-time decision process),即多阶段决策过程和连续时间决策过程(continuous-time decision process);根据过程的演变是确定的还是随机的,分为确定性决策过程(deterministic decision process)随机性决策过程(stochastic decision process),其中应用最广的是确定性多阶段决策过程。
4、动态规划模型的基本要素
一个多阶段决策过程最优化问题的动态规划模型通常包含以下要素:
1)阶段
阶段(step)是对整个过程的自然划分。通常根据时间顺序或空间特征来划分阶段,以便按阶段的次序解优化问题。阶段变量一般用k=1,2,..,n表示。在例1中由A出发为k=1,由Bi(i=1,2)出发为k=2,依此下去从Di(i=1,2,3)出发为k=4,共n=4个阶段。在例2中按照第一、二、三、四季度分为k=1,2,3,4,共4个阶段。
2)状态
状态(state)表示每个阶段开始时过程所处的自然状况。它应该能够描述过程的特征并且具有无后向性,即当某阶段的状态给定时,这个阶段以后过程的演变与该阶段以前各阶段的状态无关,即每个状态都是过去历史的一个完整总结。通常还要求状态是直接或间接可以观测的。
描述状态的变量称状态变量(state variable)。变量允许取值的范围称允许状态集合(set of admissible states)。用xk表示第k阶段的状态变量,它可以是一个数或一个向量。用Xk表示第k阶段的允许状态集合。在例1中x2可取B1,B2,X2={B1,B2}。
n个阶段的决策过程有n+1个状态变量,xn+1表示xn演变的结果,在例1中x5取E。
根据过程演变的具体情况,状态变量可以是离散的或连续的。为了计算的方便有时将连续变量离散化;为了分析的方便有时又将离散变量视为连续的。
状态变量简称为状态
3)决策
当一个阶段的状态确定后,可以作出各种选择从而演变到下一阶段的某个状态,这种选择手段称为决策(decision),在最优控制问题中也称为控制(control)
描述决策的变量称决策变量(decision variable)。变量允许取值的范围称允许决策集合(set of admissible decisions)。用uk(xk)表示第k阶段处于状态xk时的决策变量,它是xk的函数,用Uk(xk)表示了xk的允许决策集合。在例1中u2(B1)可取C1,C2,C3
决策变量简称决策
4)策略
决策组成的序列称为策略(policy)。由初始状态x1开始的全过程的策略记作p1n(x1),即p1n(x1)={u1(x1),u2(x2),...,un(xn)}。由第k阶段的状态xk开始到终止状态的后部子过程的策略记作pkn(xk),即pkn(xk)={uk(xk),uk+1(xk+1),...,un(xn)}。类似地,由第k到第j阶段的子过程的策略记作pkj(xk)={uk(xk),uk+1(xk+1),...,uj(xj)}。对于每一个阶段k的某一给定的状态xk,可供选择的策略pkj(xk)有一定的范围,称为允许策略集合(set of admissible policies),用P1n(x1),Pkn(xk),Pkj(xk)表示。
5)状态转移方程
在确定性过程中,一旦某阶段的状态和决策为已知,下阶段的状态便完全确定。用状态转移方程(equation of state)表示这种演变规律,写作
例1中状态转移方程为:xk+1=uk(xk)
6)指标函数和最优值函数
指标函数(objective function) 是衡量过程优劣的数量指标,它是关于策略的数量函数,从阶段k到阶段n的指标函数用Vkn(xk,pkn(xk))表示,k=1,2,...,n。
能够用动态规划解决的问题的指标函数应具有可分离性,即Vkn可表为xk,uk,Vk+1 n 的函数,记为:
这些形式下第k到第j阶段子过程的指标函数为Vkj(xk,uk,xk+1,...,xj+1)。可以发现,上述(3)-(5)三个指标函数的形式都满足最优性原理。在例1中指标函数为(3)的形式,其中vj(xj,uj)是边j,uj(xj)>的权(边的长度),uj(xj)表示从xj出发根据决策uj(xj)下一步所到达的节点。
根据状态转移方程,指标函数Vkn还可以表示为状态xk和策略pkn的函数,即Vkn(xk,pkn)。在xk给定时指标函数Vkn对pkn的最优值称为最优值函数(optimal value function),记作fk(xk),即
其中opt可根据具体情况取max或min。上式的意义是,对于某个阶段k的某个状态xk,从该阶段k到最终目标阶段n的最优指标函数值等于从xk出发取遍所有能策略pkn所得到的最优指标值中最优的一个。
7)最优策略和最优轨线
使指标函数Vkn达到最优值的策略是从k开始的后部子过程的最优策略,记作pkn*={uk*,..un*},p1n*又是全过程的最优策略,简称最优策略(optimal policy)。从初始状态x1(=x1*)出发,过程按照p1n*和状态转移方程演变所经历的状态序列{x1*,x2*,..,xn+1*}称最优轨线(optimal trajectory)
三、动态规划的基本定理和基本方程
动态规划发展的早期阶段,从简单逻辑出发给出了所谓最优性原理,然后在最优策略存在的前提下导出基本方程,再由这个方程求解最优策略。后来在动态规划的应用过程中发现,最优性原理不是对任何决策过程普遍成立,它与基本方程不是无条件等价,二者之间也不存在任何确定的蕴含关系。基本方程在动态规划中起着更为本质的作用。
[基本定理]   对于初始状态x1∈X1,策略p1n*={u1*,..un*}是最优策略的充要条件是对于任意的k,1
四、动态规划的适用条件
任何思想方法都有一定的局限性,超出了特定条件,它就失去了作用。同样,动态规划也并不是万能的。适用动态规划的问题必须满足最优化原理和无后效性。
1.最优化原理(最优子结构性质)
最优化原理可这样阐述:一个最优化策略具有这样的性质,不论过去状态和决策如何,对前面的决策所形成的状态而言,余下的诸决策必须构成最优策略。简而言之,一个最优化策略的子策略总是最优的。一个问题满足最优化原理又称其具有最优子结构性质
这两个问题看起来很相似。但实质上是不同的。为了方便讨论,我将每个顶点标记了号码。由于必然经过最右边的顶点7,所以一条路(P1-P2)可以看成两条路(P1-7)与(P2-7)的结合。所以,这个问题的状态可以用两条道路结合的形式表示。我们可以把这些状态中,两条路中起始顶点相同的状态归于一个阶段,设为阶段[P1,P2]。
那么,对于Bitonic旅行路线问题来说,阶段[P1,P2]如果可以由阶段[Q1,Q2]推出,则必须满足的条件就是:P1无后向性,可以用动态规划来解决。
有些问题乍一看好像有后向性,但如果按照某种合理的方式重新划分阶段,就可以发现其本质上是无后向性的,所以关键是阶段的合理划分,这一点将在动态规划的技巧中详细阐述。
3.子问题的重叠性
在例1中我们看到,动态规划将原来具有指数级复杂度的搜索算法改进成了具有多项式时间的算法。其中的关键在于解决冗余,这是动态规划算法的根本目的。动态规划实质上是一种以空间换时间的技术,它在实现的过程中,不得不存储产生过程中的各种状态,所以它的空间复杂度要大于其它的算法。以Bitonic旅行路线问题为例,这个问题也可以用搜索算法来解决。动态规划的时间复杂度为O(n2),搜索算法的时间复杂度为O(n!) ,但从空间复杂度来看,动态规划算法为O(n2),而搜索算法为O(n),搜索算法反而优于动态规划算法。选择动态规划算法是因为动态规划算法在空间上可以承受,而搜索算法在时间上却无法承受,所以我们舍空间而取时间。
设原问题的规模为n,容易看出,当子问题树中的子问题总数是n的超多项式函数,而不同的子问题数只是n的多项式函数时,动态规划法显得特别有意义,此时动态规划法具有线性时间复杂性。所以,能够用动态规划解决的问题还有一个显著特征:子问题的重叠性。这个性质并不是动态规划适用的必要条件,但是如果该性质无法满足,动态规划算法同其他算法相比就不具备优势。
图5 动态规划设计的一般模式
    上述提供了动态规划方法的一般模式,对于简单的动态规划问题,可以按部就班地进行动态规划的设计。
    下面,给出一个利用动态规划方法求解的典型例子。 
    【例题6】数字三角形问题。图6示出了一个数字三角形宝塔。数字三角形中的数字为不超过100的整数。现规定从最顶层走到最底层,每一步可沿左斜线向下或右斜线向下走。
  任务一:假设三角形行数≤10,键盘输入一个确定的整数值M,编程确定是否存在一条路径,使得沿着该路径所经过的数字的总和恰为M,若存在则给出所有路径,若不存在,则输出“NO Answer!”字样。
    任务二:假设三角形行数≤100,编程求解从最顶层走到最底层的一条路径,使得沿着该路径所经过的数字的总和最大,输出最大值。
    输人数据:由文件输入数据,任务一中文件第一行是三角形的行数N和整数值 M。以后的N行分别是从最顶层到最底层的每一层中的数字。任务二中文件数据格式同任务一,只是第一行中没有整数值M。在例子中任务二的文件数据表示如下:
    【分析】对于这一问题,很容易想到用枚举的方法去解决,即列举出所有路径并记录每一条路径所经过的数字总和。然后判断数字总和是否等于给定的整数值M或寻找出最大的数字总和,这一想法很直观,而且对于任务一,由于数字三角形的行数不大(<=10),因此其枚举量不是很大,应该能够实现。但对于任务二,如果用枚举的方法,当三角形的行数等于100时,其枚举量之大是可想而知的,显然,枚举法对于任务二的求解并不适用。其实,只要对对任务二稍加分析,就可以得出一个结论:
    如果得到一条由顶至底的某处的一条最佳路径,那么对于该路径上的每一个中间点来说,由顶至该中间点的路径所经过的数字和也为最大。因此该问题是一个典型的多阶段决策最优化的问题。算法设计与分析如下:
    对于任务一,合理地确认枚举的方法,可以优化问题的解法。由于从塔顶到底层每次都只有两种走法,即左或右。设“0”表示左,  “1”表示右,对于层数为N的数字塔,从顶到底的一种走法可用一个N-1位的二进制数表示。如例中二进制数字串1011,其对应的路径应该是:8—1—4—6。这样就可以用一个N—l位的二进制数来模拟走法和确定解的范围。穷举出从0到2n-1个十进制数所对应的N-1位二进制串对应的路径中的数字总和,判定其是否等于M而求得问题的解。
    对于任务二,采用动态规划中的顺推解法。按三角形的行划分阶段,若行数为 n,则可把问题看做一个n-1个阶段的决策问题。从始点出发,依顺向求出第一阶段、第二阶段……第n—1阶段中各决策点至始点的最佳路径,最终求出始点到终点的最佳路径。
    设:fk(Uk)为从第k阶段中的点Uk至三角形顶点有一条最佳路径,该路径所经过的数字的总和最大,fk(Uk)表示为这个数字和;
    由于每一次决策有两个选择,或沿左斜线向下,或沿右斜线向下,因此设:
    Uk1为k-1阶段中某点Uk沿左斜线向下的点;
    Uk2为k-1阶段中某点Uk沿右斜线向下的点;
    dk(Uk1)为k阶段中Uk1的数字;dk(Uk2)为k阶段中Uk2的数字。
    因而可写出顺推关系式(状态转移方程)为:
    fk(Uk)=max{fk-1(Uk)+dk(Uk1),fk-1(Uk)+dk(Uk2)}(k=1,2,3,…,n)
    f0(U0)=0
    经过一次顺推,便可分别求出由顶至底N个数的N条路径,在这N条路径所经过的N个数字和中,最大值即为正确答案。
六、动态规划的基本思想
前文主要介绍了动态规划的一些理论依据,我们将前文所说的具有明显的阶段划分和状态转移方程的动态规划称为标准动态规划,这种标准动态规划是在研究多阶段决策问题时推导出来的,具有严格的数学形式,适合用于理论上的分析。在实际应用中,许多问题的阶段划分并不明显,这时如果刻意地划分阶段法反而麻烦。一般来说,只要该问题可以划分成规模更小的子问题,并且原问题的最优解中包含了子问题的最优解(即满足最优子化原理),则可以考虑用动态规划解决。
动态规划的实质是分治思想解决冗余,因此,动态规划是一种将问题实例分解为更小的、相似的子问题,并存储子问题的解而避免计算重复的子问题,以解决最优化问题的算法策略。
由此可知,动态规划法与分治法贪心法类似,它们都是将问题实例归纳为更小的、相似的子问题,并通过求解子问题产生一个全局最优解。其中贪心法的当前选择可能要依赖已经作出的所有选择,但不依赖于有待于做出的选择和子问题。因此贪心法自顶向下,一步一步地作出贪心选择;而分治法中的各个子问题是独立的 (即不包含公共的子子问题),因此一旦递归地求出各子问题的解后,便可自下而上地将子问题的解合并成问题的解。但不足的是,如果当前选择可能要依赖子问题的解时,则难以通过局部的贪心策略达到全局最优解;如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题。
解决上述问题的办法是利用动态规划。该方法主要应用于最优化问题,这类问题会有多种可能的解,每个解都有一个值,而动态规划找出其中最优(最大或最小)值的解。若存在若干个取最优值的解的话,它只取其中的一个。在求解过程中,该方法也是通过求解局部子问题的解达到全局最优解,但与分治法和贪心法不同的是,动态规划允许这些子问题不独立,(亦即各子问题可包含公共的子子问题)也允许其通过自身子问题的解作出选择,该方法对每一个子问题只解一次,并将结果保存起来,避免每次碰到时都要重复计算。
因此,动态规划法所针对的问题有一个显著的特征,即它所对应的子问题树中的子问题呈现大量的重复。动态规划法的关键就在于,对于重复出现的子问题,只在第一次遇到时加以求解,并把答案保存起来,让以后再遇到时直接引用,不必重新求解。
七、动态规划算法的基本步骤
设计一个标准的动态规划算法,通常可按以下几个步骤进行:
划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。注意这若干个阶段一定要是有序的或者是可排序的(即无后向性),否则问题就无法用动态规划求解。
选择状态:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
确定决策并写出状态转移方程:之所以把这两步放在一起,是因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以,如果我们确定了决策,状态转移方程也就写出来了。但事实上,我们常常是反过来做,根据相邻两段的各状态之间的关系来确定决策。
写出规划方程(包括边界条件):动态规划的基本方程是规划方程的通用形式化表达式。一般说来,只要阶段、状态、决策和状态转移确定了,这一步还是比较简单的。
动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。根据动态规划的基本方程可以直接递归计算最优值,但是一般将其改为递推计算,实现的大体上的框架如下:
 
 
标准动态规划的基本框架
1.  对fn+1(xn+1)初始化;    {边界条件}
2.  for k:=n downto 1 do
3.      for 每一个xk∈Xk do
4.        for 每一个uk∈Uk(xk) do
            begin
5.            fk(xk):=一个极值;                 {∞或-∞}
6.            xk+1:=Tk(xk,uk);                  {状态转移方程}
7.            t:=φ(fk+1(xk+1),vk(xk,uk));       {基本方程(9)式}
8.            if  t比fk(xk)更优 then fk(xk):=t; {计算fk(xk)的最优值}
           end; 
9.  t:=一个极值;                               {∞或-∞}
10. for 每一个x1∈X1 do
11.     if f1(x1)比t更优 then t:=f1(x1);       {按照10式求出最优指标}
12. 输出t;
但是,实际应用当中经常不显式地按照上面步骤设计动态规划,而是按以下几个步骤进行:
1)        分析最优解的性质,并刻划其结构特征。
2)        递归地定义最优值。
3)        以自底向上的方式或自顶向下的记忆化方法(备忘录法)计算出最优值。
4)        根据计算最优值时得到的信息,构造一个最优解。
步骤(1)--(3)是动态规划算法的基本步骤。在只需要求出最优值的情形,步骤(4)可以省略,若需要求出问题的一个最优解,则必须执行步骤(4)。此时,在步骤(3)中计算最优值时,通常需记录更多的信息,以便在步骤(4)中,根据所记录的信息,快速地构造出一个最优解。
八、动态规划的实例分析
下面我们将通过实例来分析动态规划的设计步骤和具体应用。例1已经在前文介绍过了。例1和例2是标准的动态规划,有明显的阶段和状态转移方程;例3、例4、例5、例6是没有明显阶段划分的动态规划,也是一般常见的形式,其中对例4、例5、例6作了比较详细的分析;例7是比较特殊的动态规划,例8是两重动态规划(即为了解决问题要进行两次动态规划)的例子。
 
例1 最短路径问题
例2 生产计划问题
例3 Bitonic旅行路线问题
例4 计算矩阵连乘积
例5 最长公共子序列
例6 凸多边形的最优三角剖分问题
例7 多边形计算
例8 字符识别
更多实例请参阅动态规划问题集
 
1 生产计划问题
问题描述    工厂生产某种产品,每单位(千件)的成本为1(千元),每次开工的固定成本为3(千元),工厂每季度的最大生产能力为6(千件)。经调查,市场对该产品的需求量第一、二、三、四季度分别为 2,3,2,4(千件)。如果工厂在第一、二季度将全年的需求都生产出来,自然可以降低成本(少付固定成本费),但是对于第三、四季度才能上市的产品需付存储费,每季每千件的存储费为0.5(千元)。还规定年初和年末这种产品均无库存。试制订一个生产计划,即安排每个季度的产量,使一年的总费用(生产成本和存储费)最少。
参考解答   这是一个明显的多阶段问题,我们按照计划时间自然划分阶段,状态定义为每阶段开始时的存储量xk,决策为每个阶段的产量uk,记每个阶段的需求量(已知)为dk,则状态转移方程为:
设每个阶段开工固定成本费用为a,生产单位数量产品的成本为b,每阶段单位数量产品的存储费用为c,阶段指标为阶段的生产成本费用和存储费用之和,即:
指标函数Vkn为vk之和,最优值函数fk(xk)为从第k阶段的状态xk出发到过程终结的最小费用,满足
其中允许决策集合Uk由每阶段的最大生产能力决定,设过程终结时允许存储量为x0n+1,则终端条件为:
将以上各式代入到标准动态规划的框架中,就可以求得最优解。
2  Bitonic旅行路线问题
问题描述   欧几里德货郎担问题是对平面给定的n个点确定一条连结各点的、闭合的游历路线问题。图7(a)给出了七个点问题的解。Bitonic旅行路线问题是欧几里德货郎担问题的简化,这种旅行路线先从最左边开始,严格地由左至右到最右边的点,然后再严格地由右至左到出发点,求路程最短的路径长度。图7(b)给出了七个点问题的解。
请设计一种多项式时间的算法,解决Bitonic旅行路线问题。
参考解答    首先将n个点按X坐标递增的顺序排列成一个序列L=<点1,点2,…,点n>。显然L[n]为最右点,L[1]为最左点。由于点1往返经过二次(出发一次,返回一次),因此点1拆成两个点:L[0]=L[1]=点1。
设:
Wi,j -- 边的距离;
由该公式知计算C=AB总共需要pqr次的数乘。
现在的问题是,给定n个矩阵{A1,A2,…,An}。其中Ai与Ai+1是可乘的,i=1,2,…,n-1。要求计算出这n个矩阵的连乘积A1A2…An
    由于矩阵乘法满足结合律,故连乘积的计算可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。若一个矩阵连乘积的计算次序已完全确定,也就是说该连乘积已完全加括号,则我们可以通过反复调用两个矩阵相乘的标准算法计算出矩阵连乘积。完全加括号的矩阵连乘积可递归地定义为:
单个矩阵是完全加括号的;
若矩阵连乘积A是完全加括号的,则A可表示为两个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)。
例如,矩阵连乘积A1A2A3 A4可以有以下5种不同的完全加括号方式:
        (A1(A2(A3A4))),
        (A1((A2A3)A4)),
        ((A1A2)(A3A4)),
        ((A1(A2A3))A4),
        (((A1A2)A3)A4)。
每一种完全加括号方式对应于一种矩阵连乘积的计算次序,而这种计算次序与计算矩阵连乘积的计算量有着密切的关系。
为了说明在计算矩阵连乘积时加括号方式对整个计算量的影响,我们来看一个计算3个矩阵{A1,A2,A3}的连乘积的例子。设这3个矩阵的维数分别为10×100,100×5和5×50。若按第一种加括号方式((A1A2)A3)来计算,总共需要10×100×5+10×5×50=7500次的数乘。若按第二种加括号方式(A1(A2A3))来计算,则需要的数乘次数为100×5×50+10×100×50=75000。第二种加括号方式的计算量是第一种加括号方式的计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大影响。
于是,人们自然会提出矩阵连乘积的最优计算次序问题,即对于给定的相继n个矩阵{A1,A2,…,An}(其中Ai的维数为pi-1×pi ,i=1,2,…,n),如何确定计算矩阵连乘积A1A2…An的一个计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。
说明:计算两个均为n×n的矩阵(即n阶方阵)相乘还有一种Strassen矩阵乘法,利用分治思想将2个n阶矩阵乘积所需时间从标准算法的O(n3)改进到O(nlog7)=O(n2.81)。目前计算两个n阶方阵相乘最好的计算时间上界是O(n2.367)。但无论如何,所需的乘法次数总随两个矩阵的阶而递增。在这道题中只考虑采用标准公式计算两个矩阵的乘积。
参考解答    解这个问题的最容易想到的方法是穷举搜索法。也就是列出所有可能的计算次序,并计算出每一种计算次序相应需要的计算量,然后找出最小者。然而,这样做计算量太大。事实上,对于n个矩阵的连乘积,设有P(n)个不同的计算次序。由于我们可以首先在第k个和第k+1个矩阵之间将原矩阵序列分为两个矩阵子序列,k=1,2,…,n-1;然后分别对这两个矩阵子序列完全加括号;最后对所得的结果加括号,得到原矩阵序列的一种完全加括号方式。所以关于P(n),我们有递推式如下:
    下面我们来考虑用动态规划法解矩阵连乘积的最优计算次序问题。此问题是动态规划的典型应用之一。
1.分析最优解的结构
    首先,为方便起见,将矩阵连乘积AiAi+1…Aj简记为Ai…j。我们来看计算A1…n的一个最优次序。设这个计算次序在矩阵Ak和Ak+1之间将矩阵链断开,1<=k1…Ak)(Ak+1…An))。照此,我们要先计算A1…k和Ak+1…n,然后,将所得的结果相乘才得到A1…n。显然其总计算量为计算A1…k的计算量加上计算Ak+1…n的计算量,再加上A1…k与Ak+1…n相乘的计算量。
    这个问题的一个关键特征是:计算A1…n的一个最优次序所包含的计算A1…k的次序也是最优的。事实上,若有一个计算A1…k的次序需要的计算量更少,则用此次序替换原来计算A1…k的次序,得到的计算A1…n的次序需要的计算量将比最优次序所需计算量更少,这是一个矛盾。同理可知,计算A1…n的一个最优次序所包含的计算矩阵子链Ak+1…n的次序也是最优的。根据该问题的指标函数的特征也可以知道该问题满足最优化原理。另外,该问题显然满足无后向性,因为前面的矩阵链的计算方法和后面的矩阵链的计算方法无关。
2.建立递归关系
    对于矩阵连乘积的最优计算次序问题,设计算Ai…j ,1≤i≤j≤n,所需的最少数乘次数为m[i,j],原问题的最优值为m[1,n]。
当i=j时,Ai…j=Ai为单一矩阵,无需计算,因此m[i,i]=0,i=1,2,…,n ;
当ii…j的最优次序在Ak和Ak+1之间断开,i≤ki-1pkpj
    由于在计算时我们并不知道断开点A的位置,所以A还未定。不过k的位置只有j-i个可能,即k∈{i,i+1,…,j-1}。因此k是这j-i个位置中计算量达到最小的那一个位置。从而m[i,j]可以递归地定义为:
   用动态规划算法解此问题,可依据递归式(2.1)自底向上的方式进行计算,在计算过程中,保存已解决的子问题答案,每个子问题只计算一次,而在后面需要时只要简单查一下,从而避免大量的重复计算,最终得到多项式时间的算法。下面所给出的计算m[i,j]动态规划算法中,输入是序列P={p0,p1,…,pn},输出除了最优值m[i,j]外,还有使
m[i,j] = m[i,k] + m[k+1,j] + pi-1pkpj
达到最优的断开位置k=s[i,j],1≤i≤j≤n 。
 
Procedure MATRIX_CHAIN_ORDER(p); {计算矩阵链连乘的最优断开位置}
var
i,j,k,q:integer;
begin
  for j:=1 to n do {矩阵链的长度为n}
  for i:=j downto 1 do
    begin
      if i=j then m[i,j]:=0
             else begin
                    m[i,j]:=∞;
                    for k:=i to j-1 do
                      begin
                        q:=m[i,k]+m[k+1,j]+p[i-1]*p[k]*p[j];
                        if q
                            begin
                              m[i,j]:=q;
                              s[i,j]:=k;
                               {s[i,j]记录计算A[i..j]的最优断开位置k}
                            end;
                      end;
                  end;
    end;
end;
该算法按照
    m[1,1]
    m[2,2]   m[1,2]
    m[3,3]   m[2,3]   m[1,3]
    ...      ...      ...
    m[n,n]   m[n-1,n] ... ....  ... m[1,n]
的顺序根据公式(2.1)计算m[i,j]。
该算法的计算时间上界为O(n3)。算法所占用的空间显然为O(n2)。由此可见,动态规划算法比穷举搜索法要有效得多。
4.构造最优解
    算法MATRIX_CHAIN_ ORDER只是计算出了最优值,并未给出最优解。也就是说,通过MATRIX_CHAIN_ORDER的计算,我们只知道计算给定的矩阵连乘积所需的最少数乘次数,还不知道具体应按什么次序来做矩阵乘法才能达到数乘次数最少。
    然而,MATRIX_CHAIN_ORDER己记录了构造一个最优解所需要的全部信息。事实上,s[i,j]中的数k告诉我们计算矩阵链Ai…j的最佳方式应在矩阵Ak和Ak+1之间断开,即最优的加括号方式应为(A1...k)(Ak+1…n)。因此,从s[i,j]记录的信息可知计算A1…n的最优加括号方式为 (A1…s[1,n])(As[1,n]+1…n)。而计算A1…s[1,n]的最优加括号方式为(A1…s[1,s[1,n]])(As[1,s[1,n]]+1…s[1,n])。同理可以确定计算As[1,n]+1…n的最优加括号方式在s[s[1,n]+1,n]处断开。…照此递推下去,最终可以确定As[1,n]+1…n的最优完全加括号方式,即构造出问题的一个最优解。
    下面的算法MATRIX_CHAIN_MULTIPLY(A,s,i,j)是按s指示的加括号方式计算矩阵链A={A1,A2,…,An}的子链Ai…j的连乘积的算法。
Procdeure MATRIX_CHAIN_MULTIPLY(A,s,i,j);
begin
  if j>i then
           begin
             X←MATRIX_CHAIN_MULTIPLY(A,s,i,s[i,j]);
             Y←MATRIX_CHAIN_MULTIPLY(A,s,s[i,j]+1,j);
             return(MATRIX_MULTIPLY(X,Y)); {计算并返回矩阵X*Y的值}
           end
         else return(Ai);
end;
要计算A1…n只要调用MATRIX_CHAIN_MULTIPLY(A,s,1,n)即可。
    从算法MATRIX_CHAIN_ ORDER可以看出,该算法的有效性依赖于问题本身所具有的三个重要性质:最优子结构性质无后向性子问题重叠性质。一般说来,问题所具有的这三个重要性质是该问题可用动态规划算法求解的基本要素,这对于我们在设计求解具体问题的算法时,是否选择动态规划算法具有指导意义。下面我们着重研究最优子结构性质子问题重叠性质以及动态规划法的一个变形—备忘录方法
最优子结构
设计动态规划算法的第1步通常是要刻划最优解的结构。当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。问题的最优子结构性质提供了该问题可用动态规划算法求解的重要线索。
在矩阵连乘积最优计算次序问题中,我们注意到,若A1…n的最优完全加括号方式在Ak和Ak+1之间将矩阵链断开,则由该次序确定的子链A1…k和Ak+1…n的完全加括号方式也是最优的。也就是说该问题具有最优子结构性质。在分析该问题的最优子结构性质时,我们所用的方法是具有普遍性的。我们首先假设由问题的最优解导出的其子问题的解不是最优的,然后再设法证明在这个假设下可构造出一个比原问题最优解更好的解,从而导致矛盾。
在动态规划算法中,问题的最优子结构性质使我们能够以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。同时,它也使我们能在相对小的子问题空间中考虑问题。例如,在矩阵连乘积最优计算次序问题中,子问题空间是输人的矩阵链的所有不同的子链,它们的个数为θ(n2)。因而子问题空间的规模仅为θ(n2)。
重叠子问题
可用动态规划算法求解的问题应具备的另一基本要素是子问题的重叠性质。也就是说,在用递归算法自顶向下解此问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只解一次,而后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。通常,不同的子问题的个数随输人问题的大小呈多项式增长。因此,用动态规划算法通常只需要多项式时间,从而获得较高的解题效率。
为了说明这一点,我们来看在计算矩阵连乘积最优计算次序时,利用公式(2.1)直接计算Ai…j的递归算法。
Function RECURSIVE_MATRIX_CHAIN(P,i,j);
begin
  if i=j then return(0);
  m[i,j]:=∞;
  for k:=i to j-1 do
    begin
      q:=RECURSIVE_MATRIX_CHAIN(P,i,k)+RECURSIVE_MATRIX_CHAIN(P,k+1,j)+pi-1pkpj;
      if q
    end
  return(m[i,j]);
end;
因此,直接递归算法RECURSIVE_MATRIX_CHAIN(P,1,n)的计算时间随n指数增长。相比之下,解同一问题的动态规划算法MATRIX_CHAIN_ORDER (P,1,n)只需计算时间O(n2)。其有效性就在于它充分利用了问题的子问题重叠性质。不同的子问题个数为θ(n2),而动态规划算法对于每个不同的子问题只计算一次,不是重复计算多次。由此也可看出,当解某一问题的直接递归算法所产生的递归树中,相同的子问题反复出现,并且不同子问题的个数又相对较少时,用动态规划算法是有效的。
备忘录方法
动态规划算法的一个变形是备忘录方法。与动态规划算法一样,备忘录方法用一个表格来保存已解决的子问题的答案,在再碰到该子问题时,只要简单地查看该子问题的解答,而不必重新求解。不同的是,备忘录方法采用的是自顶向下的递归方式,而动态规划算法采用的是自底向上的非递归方式。我们看到,备忘录方法的控制结构与直接递归方法的控制结构相同,区别仅在于备忘录方法为每个解过的子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解。
备忘录方法为每个子问题建立一个记录项,初始化时,该记录项存入一个特殊的值,表示该子问题尚末求解。在求解过程中,对碰到的每个子问题,首先查看其相应的记录项。若记录项中存储的是初始化时存入的特殊值,则表示该子问题是第一次遇到,此时需要对该子问题进行求解,并把得到的解保存在其相应的记录项中,以备以后查看。若记录项中存储的巴不是初始化时存入的特殊值,则表示该子问题己被求解过,其相应的记录项中存储的是该子问题的解答。此时,只要从记录项中取出读子问题的解答即可,不必重新计算。
下面的算法MEMOIZED_MATRIX_CHAIN(P)是解矩阵连乘积最优计算次序问题的备忘录方法。
     
   Procedure MEMOIZED_MATRIX_CHAIN(P);
      begin
       n:=length[P]-1;
       for i:=1 to n do
         for j:=1 to n do
           m[i,j]:=∞;
       return(LOOKUP_CHAIN(P,l,n));
      end;
     
      Function LOOKUP_CHAIN(P,i,j);
      begin
       if m[i,j]<∞ then return(m[i,j]);
       if i=j the m[i,j]:=0
        else
         for k:=i to j-1 do
          begin
           q:=LOOKUP_CHAIN(P,i.,k)+LOOKUP_CHAIN(P,k+1,j)+pi-1pkpj;
           if q
          end;
        return(m[i,j]);
       end;     
与动态规划算法MATRIX_CHAIN_ORDER一样,备忘录算法MEMOIZED_ MATRIX_CHAIN用数组m[1…n,1…n]的单元m[i,j]来记录解子问题Ai…j的最优计算量。M[i,j]初始化为∞,表示相应于Ai…j的子问题还未被计算。在调用LOODKUP _CHAIN(P,i,j)时,若m[i,j]<∞,则表示m[i,j]中存储的是所要求子问题的计算结果,直接返回此结果即可。否则与直接递归算法一样,自顶向下地递归计算,并将计算结果存入m[i,j]后返回。因此,LOODKUP_CHAIN(P,i,j)总能正确返回m[i,j]的值,但仅在它第一次被调用时计算,以后的调用就直接返回计算结果。
与动态规划算法一样,备忘录算法MEMOIZED_MATRIX_CHAIN耗时O(n3)。事实上,共有O(n2)个备忘记录项m[i,j],i=1,2,…,n , j=i,i+1,…n,这些记录项的初始化耗费O(n2)时间。每个记录项只填入一次,每次填入时,不包括填入其他记录项的时间,共耗费O(n)。因此,LOODKUP_CHAIN(P,1,n)填入O(n2)个记录项总共耗费O(n3)计算时间。由此可见,通过使用备忘录技术,直接递归算法的计算时间从仍Ω(2n)降至O(n3)。
综上所述,矩阵连乘积的最优计算次序问题可用自顶向下的备忘录算法或自底向上的动态规划算法在O(n3)时间内求解。这两个算法都利用了子问题重叠性质。总共有θ(n2)个不同的子问题。对每个子问题,两种方法都只解一次,并记录答案,再碰到该问题时,不重新求解而简单地取用已得到的答案。因此,节省了计算量,提高了算法的效率。
一般地讲,当一个问题的所有子问题都至少要解一次时,用动态规划算法解比用备忘录方法好。此时,动态规划算法没有任何多余的计算。同时,对于许多问题,常可利用其规则的表格存取方式,来减少在动态规划算法中的计算时间和空间需求。当子问题空间中的部分子问题可不必求解时,用备忘录方法则较有利,因为从其控制结构可以看出,该方法只解那些确实需要求解的子问题。
最长公共子序列问题LCS
问题描述   一个给定序列的子序列是在该序列中删去若干元素后得到的序列。确切地说,若给定序列X=<x1, x2,…, xm>,则另一序列Z=<z1, z2,…, zk>是X的子序列是指存在一个严格递增的下标序列 <i1, i2,…, ik>,使得对于所有j=1,2,…,k有
例如,序列Z=是序列X=的子序列,相应的递增下标序列为<2,3,5,7>。
给定两个序列XY,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。例如,若X=和Y=,则序列是X和Y的一个公共子序列,序列也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列,因为X和Y没有长度大于4的公共子序列。
最长公共子序列(LCS)问题:给定两个序列X=<x1, x2, …, xm>和Y=<y1, y2, … , yn>,要求找出X和Y的一个最长公共子序列。
参考解答   动态规划算法可有效地解此问题。下面我们按照动态规划算法设计的各个步骤来设计一个解此问题的有效算法。
1.最长公共子序列的结构
解最长公共子序列问题时最容易想到的算法是穷举搜索法,即对X的每一个子序列,检查它是否也是Y的子序列,从而确定它是否为X和Y的公共子序列,并且在检查过程中选出最长的公共子序列。X的所有子序列都检查过后即可求出X和Y的最长公共子序列。X的一个子序列相应于下标序列{1, 2, …, m}的一个子序列,因此,X共有2m个不同子序列,从而穷举搜索法需要指数时间。
事实上,最长公共子序列问题也有最优子结构性质,因为我们有如下定理:
定理: LCS的最优子结构性质
设序列X=1, x2, …, xm>和Y=1, y2, …, yn>的一个最长公共子序列Z=1, z2, …, zk>,则:
若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列;
若xm≠yn且zk≠xm 则Z是Xm-1和Y的最长公共子序列;
若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。
其中Xm-1=1, x2, …, xm-1>,Yn-1=1, y2, …, yn-1>,Zk-1=1, z2, …, zk-1>。
证明   用反证法。若zk≠xm,则1, z2, …, zk ,xm >是X和Y的长度为k十1的公共子序列。这与Z是X和Y的一个最长公共子序列矛盾。因此,必有zk=xm=yn。由此可知Zk-1是Xm-1和Yn-1的一个长度为k-1的公共子序列。若Xm-1和Yn-1有一个长度大于k-1的公共子序列W,则将xm加在其尾部将产生X和Y的一个长度大于k的公共子序列。此为矛盾。故Zk-1是Xm-1和Yn-1的一个最长公共子序列。
由于zk≠xm,Z是Xm-1和Y的一个公共子序列。若Xm-1和Y有一个长度大于k的公共子序列W,则W也是X和Y的一个长度大于k的公共子序列。这与Z是X和Y的一个最长公共子序列矛盾。由此即知Z是Xm-1和Y的一个最长公共子序列。
与 2.类似。
这个定理告诉我们,两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列。因此,最长公共子序列问题具有最优子结构性质
2.子问题的递归结构
由最长公共子序列问题的最优子结构性质可知,要找出X=1, x2, …, xm>和Y=1, y2, …, yn>的最长公共子序列,可按以下方式递归地进行:当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。
由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。
与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi