动态规划

来源:互联网 发布:pc6软件下载中心 编辑:程序博客网 时间:2022/08/17 11:22

  1、矩阵连乘问题:两个矩阵相乘的标准算法如下:

  A的维数为p*q,B的维数为q*r,则乘积C的维数为p*r,在上述三重循环中,总共需要pqr次数乘。多个矩阵连乘时,不同的计算次序其计算量会有很大的不同。例如考虑维数为10*100,100*5和5*50的三个矩阵连乘,若先计算前两个矩阵,则数乘次数为10*100*5+10*5*50=7500。若先计算后两个矩阵,则为100*5*50+10*100*50=75000,是前一种的10倍。
  设矩阵A[i]的维数为p[i-1]*p[i],i=1,2,…,n。如何确定计算矩阵连乘积A[1]A[2]…A[n]的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
  若用穷举法,需要遍历所有可能的计算次序,然后找出数乘次数最少的计算次序。对n个矩阵连乘,设有P(n)个不同的计算次序,则我们可以先分别计算子序列A[1:k]和子序列A[k+1:n],然后计算这两个结果的乘积。递归方程为n=1时P(n)=1;n>1时P(n)=P(1)P(n-1)+P(2)P(n-2)+…+P(n-1)P(1)。P(n)实际上是Catalan数,随n的增长呈指数增长。这不是一个有效有算法。
  用动态规划算法:
  (1)找出最优子结构性质:即原问题的最优解包含了其子问题的最优解。这说明可以用子问题的最优解来构造原问题的最优解。A[1:n]的最优计算次序中,其子序列A[1:k]和A[k+1,n]的对应计算次序也是最优的。只要用反证法即可,若A[1:k]还存在更优的计算次序,用它替换原来A[1:k]的计算次序,得到的解比原来最优解的计算量更少,导致矛盾。对A[k+1,n]也类似。
  (2)递归定义最优值:最优值是数乘次数。设m[i][j]表示计算A[i:j]的最少数乘次数,则原问题的最优值为m[1][n]。i=j时只有一个矩阵,故m[i][i]=0。i<j时利用最优子结构性质来计算m[i][j]。若计算A[i:j]的最优次序在A[k]和A[k+1]之间断开(i<=k<j),则m[i][j]=m[i][k]+m[k+1][j]+p[i-1]p[k]p[j]。我们事先并不知道k的具体位置,但k的范围只能在区间[i,j)内,搜索这个区间,找出使m[i][j]最小的k,即m[i][j]=min{m[i][k]+m[k+1][j]+p[i-1]p[k]p[j]},其中i<=k<j。为构造出最优解(即最优计算次序),用s[i][j]记录对应于m[i][j]的断开位置k,这样在计算出m[i][j]后,可由s[i][j]构造出原问题的最优解。
  (3)找出子问题重叠性质:由最优值m[i][j]的递归定义,可以直接写一个递归算法RecurMatrix(1,n)自顶向下地计算m[1][n]。但这样很多子问题会被重复计算多次,例如RecurMatrix(1,4)会展开到RecurMatrix(2,4)和RecurMatrix(1,3),而这两个子问题都会展开到RecurMatrix(2,3),导致这个子问题被重复计算。由于m[1][n]会展开成所有的子问题,而每个子问题都要搜索整个子空间来找出断开位置k,导致递归算法的运行时间为指数级(实际上>=2**(n-1))。实际上不同的有序对(i,j)对应不同的子问题(包括i=j的情况),因此子问题最多个数为n(n-1)/2+n=O(n**2),与2**(n-1)运行时间对比,可见有大量的子问题被重复计算。子问题之间有依赖,这是与分治法的主要区别。虽然都是分解成若干个子问题分别求解,然后合并得到原问题的解,但分治法中分解成的各个子问题是相互独立的,因此用递归算法时无重复计算。而这里RecurMatrix(2,4)和RecurMatrix(1,3)并不相互独立,都依赖于RecurMatrix(2,3),如果用分治递归算法,会由于重复计算导致指数级的时间增长。
  (4)以自底向上方式计算最优值:由于重复计算次数远超过子问题个数,这就启示我们可以先保存这些子问题的结果,以后要用到时直接获取它,从而避免了大量的重复计算。我们可以根据m[i][j]的递归式特征,以自底向上的方式计算最优值,无需递归调用。由于m[i][j]保存了已解决的子问题答案,就可以避免重复计算。这就是动态规划算法。实现如下:

  算法先计算m[i][i]=0,然后按矩阵链长递增方式计算m[i][i+1](矩阵链长为2),m[i][i+2](矩阵链长为3),...。在计算m[i][j]时,用到了已计算出的m[i][k]和m[k+1][j],这样每个子问题只计算了一次,当需要再次用到某个子问题的解时,直接读取已保存的值,无需再次计算。算法运行时间为O(n**3),空间为O(n**2)。可见若直接递归算法中子问题大量重叠,但不同子问题的个数又相对较少时,用动态规划算法非常有效。
  (4)根据计算最优值得到的信息构造最优解。s[i][j]保存了计算A[i:j]的最佳断开位置k,即最优计算次序是(A[i:k])(A[k+1,j]),据此可递归地构造出计算A[1:n]的最优计算次序。

  2、最长公共子序列问题:子序列是指在原序列中删去若干元素(这些元素可以不相邻)后得到的序列。例如X=abcbdab,Y=bdcaba,bca和bcba都是X和Y的公共子序列,且后者是最长的公共子序列。给定两个序列X(m)={x[1],x[2],...x[m]}和Y(n)={y[1],y[2],y[n]},如何找出它们的一个最长公共子序列Z(k)={z[1],...,z[k]}。
  动态规划算法求解过程如下:
  (1)最优子结构性质:两个序列的最长公共子序列包含了这两个序列的前缀的最长公共子序列。即
  1)若x[m]=y[n],则z[k]=x[m]=y[n],且Z(k-1)是X(m-1)和Y(n-1)的最长公共子序列;
  2)若x[m]!=y[n]且z[k]!=x[m],则Z(k)是X(m-1)和Y(n)的最长公共子序列;
  3)若x[m]!=y[n]且z[k]!=y[m],则Z(k)是X(m)和Y(n-1)的最长公共子序列。
  用反证法很容易证明这些结论。
  (2)递归定义最优值:最优值为公共子序列的长度。设c[i][j]表示X(i)和Y(j)的最长公共子序列长度。当i=0或j=0时,c[i][j]=0。当i,j>0时,若x[i]=y[j],找出X(m-1)和Y(n-1)的最长公共子序列,然后其尾部加上x[m](=y[n]),即得X(m)和Y(n)的最长公共子序列,因此c[i][j]=c[i-1][j-1]+1。若x[i]!=y[j],必须解决两个子问题,即找出X(m-1)和Y(m)的一个最长公共子序列及X(m)和Y(n-1)的一个最长公共子序列,这两者中的较长者即为原问题的解,因此c[i][j]=max{c[i][j-1],c[i-1][j]}。为构造最优解,用b[i][j]标识c[i][j]是由上述三种子问题中的哪一种计算出来的,是由X(i-1)和Y(j-1)的LCS在尾部加上x[i]所得到,还是由X(i-1)和Y(j)的LCS得到,还是由X(i)和Y(j-1)的LCS得到。根据b[i][j]我们可以递归地构造出最优解。
  (3)子问题重叠性质:计算LCSLength(X(m-1),Y(m))和LCSLength(X(m),Y(n-1))时,对LCSLength(X(m-1),Y(n-1))有重叠。当继续自顶向下展开时,会导致大量子问题重复计算。可见,用递归算法会导致指数级的耗时。实际上总共只有O(mn)个子问题。
  (4)自底向上地计算最优值:根据递归式自底向上地来计算c[i][j]。如下:

  显然,算法耗时O(mn)。
  (5)根据计算最优值得到的信息构造最优解:b[i][j]保存了计算c[i][j]的方式,据此可构造出最优解。

  每一次递归调用使i或j减1,因此该算法的计算时间为O(m+n)。
  3、最大子段和问题:给定n个整数a[1],a[2],...,a[n](可能为负整数),求该序列中任何连续子向量中的最大值,即形如a[i]+a[i+1]+...+a[j]的子段和的最大值。当所有元素均为负整数时,定义其最大子段和为0。若用穷举法求解,则i,j都要从1遍历到n,显然至少耗时O(n**2)。下面用动态规划算法。
  (1)最优子结构性质:对问题a[1:n],a[i]+a[i+1]+...+a[n]求和最大的子段a[i],...a[n]}称为a[1:n]的最大后缀子段。则问题a[1:n]的最大后缀子段a[i],...,a[n]包含了子问题a[1:n-1]的最大后缀子段,即a[i],...,a[n-1]是子问题a[1:n-1]的最大后缀子段。否则用a[1:n-1]真正的最大后缀子段替换原来的部分会导致矛盾。而我们所求的a[1:n]的最大子段是所有子问题a[1:1],a[1:2],...,a[1:n-1],a[1:n]的最大后缀子段中的最大者。我们把原问题转化成了求最大后缀子段的问题。
  (2)递归定义最优值:最优值为最大后缀子段和。设b[j]为a[1:j]的最大后缀子段和。当b[j-1]>0时,b[j]=b[j-1]+a[j];当b[j-1]=0时,b[j]=a[j]。通过b[j],我们就可以构造出原问题的最优解,即b[1]~b[n]中的最大者为原问题的最大子段和。据此可直接给出动态规划算法:

  该算法耗时O(n),比穷举法更高效。
  4、一般背包问题和0-1背包问题:给定n种物品,价值分别为实数v[1],v[2],...,v[n],重量分别为正整数w[1],w[2],...,w[n]。有一个背包其容量为整数c,问应如何选择装入背包中的物品,使得装入背包中的物品的总价值最大。这里每种物品可以不装入,也可以装入多次,但不能只装入一个物品的某一部分。
  这其实就是一般的整数线性规划问题。给定约束条件w[1]x[1]+w[2]x[2]+...+w[n]x[n]<=c,x[i]为非负整数,求使max{v[1]x[1]+v[2]x[2]+...+v[n]x[n]}最大的向量解(x[1],x[2],...,x[n])。当每种物品只能最多装入一次,即x[i]只能为0或1时,退化为0-1背包问题。下面用动态规划来求解:
  (1)最优子结构性质:原问题的最优解包含了子问题的最优解。设(y[1],...,y[n])是原问题的一个最优解,则(y[1],...,y[n-1])是下面子问题的最优解:max{v[1]x[1]+...+v[n-1]x[n-1]},约束条件为w[1]x[1]+...+w[n-1]x[n-1]<=c-w[n]y[n],x[i]为非负整数。若不然,设(z[1],...,z[n-1])是这个子问题的最优解,而(y[1],...,y[n-1])不是它的最优解,由可构造出原问题的一个更优解(z[1],...,z[n-1],y[n]),导致矛盾。
  (2)递归定义最优值:最优值为总价值,要建立原问题最优值与子问题最优值的关系。设m[i][j]表示子问题max{v[1]x[1]+...v[i]x[i]},w[1]x[1]+...+w[i]x[i]<=j的最优值,即m[i][j]是有i种可选物品,背包容量为j时的背包问题最优值。当i或j等于0时,m[i][j]=0。当i,j>0时,若第i种物品恰好装入k个时使总价值达到最大(即x[i]=k),则由最优子结构性质有m[i][j]=m[i-1][j-kw[i]]+kv[i]。我们事先并不知道k的值,但k的范围只能在闭区间[0,j/w[i]]内,搜索这个区间,找出使m[i][j]最大的k,并把这个k存入x[i],即m[i][j]=max{m[i-1][j-kw[i]]+kv[i]},其中k遍历0~j/w[i]。注意j<w[i]时闭区间退化为一个值0,显然有x[i]=0。对0-1背包问题,k只能为0或1,只要搜索0和1即可。在计算最优值的过程中,最优解也构造出来了。原问题的最优值为m[n][c]。算法如下:

  设各种物品中重量最小的为w,则算法运行时间O(nc(c/w))。对0-1背包问题,由于k只搜索0和1,则运行时间为O(nc),当背包容量很大时,例如c=2**n时,0-1背包问题比较耗时,需要O(n*2**n)的运行时间。
  动态规划算法的基本特征:最优子结构性质、子问题重叠性质(子问题之间有依赖导致重复计算)。
  动态规划算法的基本思想:常用于解决最优化问题。基本步骤为找出最优子结构性质、递归定义最优值、自底向上地计算最优值、根据计算最优值得到的信息构造最优解。