| 第九日:动态规划今天开始来点有意思的
 分治思想将问题分解为独立的子问题
 解决子问题,并将答案合并就可以得到总问题的解
 但是,很多情况下,分解开的子问题之间并不是相互独立的
 动态规划方法利用了这种相关性,给出了一种通用的解决方案
 
 动态规划可以解决这样一类问题:
 1、如果问题的解决是需要分阶段的(可以分解为子问题)
 2、每个子问题的解决都依赖于之前阶段子问题的解决,也就是说,存在子子问题,子问题和子子问题之间存在相关性,子问题共享子子问题的解
 3、每个子子问题只出现一次,或者说,只有一个答案,存在最优解。这样,我们就可以建一个表,把它们记下来
 用空间换取时间,这个道理说出来,大家都能理解
 可是真正第一个提出问题并想到这种解决方法的人,无疑是天才
 
 动态规划通常用于解决“最优问题”
 这类问题通常存在很多解决方案,每个方案都对应一个值
 我们希望求出得到最优(最大、最小)值的那个解决方案
 
 动态规划算法的解决步骤可以采取以下四步
 1、刻画最优解的结构
 2、以递归的方法求最优解
 3、回推,求得最优解的值
 
 举一个矩阵连乘的例子
 有一个矩阵序列(A_1,A_2,...,A_n)
 如果我们希望计算矩阵的乘积A_1A_2...A_n
 矩阵乘法仅定义了两两相乘的规则
 一个p*q的矩阵和一个q*r的矩阵相乘,最终获得一个p*r的矩阵
 算法如下:
 MATRIX-MULTIPLY(A,B )
 1 if columns[A] <> rows[B] //只有第一个矩阵的列数和第二个矩阵的行数相同,两个矩阵才可以相乘
 2   then error "矩阵不相容"
 3   else for i <- 1 to rows[A]
 4           do for j <- 1 to columns[B]
 5                 do C[i,j] <- 0
 6                    for k <- 1 to columns[A]
 7                       do C[i,j] <- C[i,j] + A[i,k] * B[k,j]
 8        return C
 
 可以看出,为了计算两个矩阵的乘积,一共要进行(p*q*r)次乘法运算
 我们的目标是要尽量减少连乘时乘法运算的次数,以加快运算速度
 
 以三个矩阵连乘为例
 我们有以下两种种计算最终结果的运算顺序
 A_1(A_2A_3)
 (A_1A_2)A_3
 假设它们分别是10×100,100×5,5×50的矩阵
 那么,第一种顺序的乘法次数为:100*5*50+10*100*50 = 25000+50000 = 75000
 第二种顺序的乘法次数为: 10*100*5+10*5*50 = 5000+2500 = 7500
 差距还是很大di
 
 考虑这个问题的特点,乘法次数与矩阵的内容无关,仅与矩阵的下标相关
 我们要做的就是给矩阵加括号,事实上也就是给下标加括号
 n个矩阵连乘,加括号的方法一共有
 P(n)={
 1, if n = 1
 sigma(k=1, to n-1, P(k)P(n-k)), if n >= 2
 }
 将相邻矩阵的重复下标去除后,矩阵序列A的下标序列记为:
 p=(p_0,p_1,...,p_n)
 
 解决问题的第一步是找到最优解的结构,也就是如何通过子问题的最优解构建总问题的最优
 我们将子问题的最优解记为m[i,j],如果求得了i=1,j=n时得m,问题就解决了
 对于n个矩阵连乘,无论如何加括号,最终总是可以分解为两个矩阵的乘法
 可以简记为(A_1...A_i)*(A_i+1...A_n)
 前面括号里面乘积是一个矩阵,后面也是
 那么这样一组括号总的乘法次数为
 m[1,i]+m[i+1,n]+p_0*p_i*p_n
 
 然后是递推求解过程
 n个矩阵一个有n-1组括号
 所以
 m[1,n]=
 ={
 0, if n = 1
 min{m[1,k]+m[k+1,n]+p_0*p_k*p_n}, if n > 1
 }
 
 m[i,j]{
 0, if i = j
 min{m[i,k]+m[k+1,j]+p_i-1*p_k*p_j}, if i < j
 }
 
 通过序列p,可以把每个m[i,j]都计算出来,记录它们的结果
 一共需要记录(n*(n+1)/2)个结果
 
 最后,通过这个公式
 m[i,i+1]=p_i-1*p_i*p_i+1
 就可以得到最终结果了
 
 这样只是得到了最终结果的值,如果我们希望知道括号是如何加的(对连乘计算过程有意义)
 还需要在每次计算m[i,j]时,记录k的取值
 
 循环形式的具体算法:
 MATRIX-CHAIN-ORDER(p)
 1 n <- length[p] - 1
 2 for i <- 1 to n
 3    do m[i,i] <- 0
 4 for l <- 2 to n //l表示链长
 5    do for i <- l to n-l+1
 6          do j <- i+l-1
 7             m[i,j] <- infinite
 8             for k <- i to j-1
 9                do q <- m[i,k]+m[k+1,j]+p_i-1*p_k*p_j
 10                  if q < m[i,j]
 11                     then m[i,j] <- q
 12                          s[i,j] <- k
 13 return m and s
 递归形式的算法留给大家自己写一下
 
 这种方法大致运行时间为o(n^3)
 空间消耗前面计算过,是n*(n+1)/2
 
 
 作业:
 1、写出递归形式的连乘算法
 2、计算下标序列为
 (5,10,3,12,5,50,6)
 的矩阵连乘所需的最少乘法数
 试着通过直觉提出一种算法
 如果和你通过以上算法得到的答案一致的话
 能不能证明这种直觉方法总是有效的?
 |