动态规划
定义:
简单来说动态规划是用来解决算法性能问题。
然而,能用动态规划的算法是很有局限的,动态规划能解决 最优子结构 问题, 其他类似的问题需要自己去归纳。 最优子结构问题 是指那种局部最优能够决定全局最优解的问题。简单来说,就是问题能够分解问子问题解决。
最优子结构问题,优点是写法清晰简单;但通常都会有一个通病,就是会重复解决同样的子问题,重复解决子问题就会带来巨大的开销。
一个简单的例子就是斐波那契数列的递归算法:
1 | public class Fibonacci { |
事实上这样的写法,N 越来越大时,由于每次都要重新冲N =1 开始计算,时间是指数倍增长。
而动态规划,就能解决这个问题。
1 | // 动态规划 |
我们每次计算会记录上一次计算结果,以便这个已计算过的树不用重复计算。
上述例子是用一个 int 变量保存上一次计算结果,动态规划也常用数组,矩阵,树等结构保存中途的计算结果。
以下来看“矩阵乘法的最优安排”这个例题。
题目:
给定n个矩阵:A1,A2,…,An,其中Ai与Ai+1是可乘的,i=1,2…,n-1。确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。输入数据为矩阵个数和每个矩阵规模,输出结果为计算矩阵连乘积的计算次序和最少数乘次数。
要解决这个题目,我们需要掌握几个知识点。
矩阵乘法:
前提:矩阵乘法必须满足 矩阵 A 的宽 = 被乘矩阵 B 的长
定理1:设矩阵 A 为 x* y (长* 宽/ 行数 * 列数), B 为 y * z,A*B = C 矩阵等于 x* z,
定理2:乘法相乘次数 = x y z
由此可知,矩阵的乘法是不满足交互律的,(AB)C !== A(BC),所以才会有最少相乘次数。
根据题目入手,相邻的矩阵必须会满足矩阵乘法的规则,相邻的矩阵相乘后,附近矩阵必须会与新矩阵相邻,满足“前提”,而解题的方法就是在于不断组合尝试相邻矩阵的,计算每次组合的乘法次数。
就以矩阵 A1, A2, A3 为例,
设 m(1,2)为A1*A2的相乘次数,m(3,3)为A3的相乘次数,
而(A1A2 ) A3 的相乘次数= A1的长 A3的长 * A3的宽
这里我们直接定义一个数组c 代表矩阵的宽(列数), A1的宽 = c1, A2的宽 = c2 …….
且这里有个关系: c1等于A2的长(行数), A1的长用 c0表示
所以 :(A1A2 ) A3 的相乘次数 = c0 c2* c3
(A1A2 ) A3 的相乘总次数 = m(1,2) + m(3,3) + c0 c2* c3
这里的m(3,3) = 0 , 对于m(k,k) 来说,都是0。
因此我们可以得出一个一般性的结论:
- 对于$A_{left} A_{left+1} ….A_{right}$ 的矩阵乘法,其相乘次数为
- 单独的$A_k$乘法次数记为 0:
算法上也很好实现
1 | /** |
但是这样就会有和上面斐波那契数列递归实现的同样问题,
且其结构跟斐波那契数列的递归实现是很像的,我们可以判断其实最优子结构问题,所以尝试用动态规划来解决这个问题。
1 | // Matrix Ai has dimension p[i-1] x p[i] for i = 1..n |
总结
动态规划,主要是解决递归算法的性能问题。
它将问题重新组合成子问题。为了避免多次解决这些子问题(带来巨大的时间复杂度开销),它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,不会在解决同样的问题时花费时间。
动态规划只能解决 最优子结构 问题。最优子结构 问题是指 局部最优 能决定全局最优解。简单来说就是问题能被分解成子问题解决。
参考资料
https://www.geeksforgeeks.org/matrix-chain-multiplication-dp-8/https://www.cnblogs.com/fornever/archive/2011/12/02/2270692.html
完)