动态规划与钢条切割
1.分治算法与动态规划
相同点:
都是通过组合子问题的解来求解原问题
不同:
1.分治将问题划分为互不相交的子问题,递归地求解子问题,在将它们的解组合起来,求出原问题。
2.动态规划应用于子问题重叠的情况,即不同的子文问题具有公共的子问题。(这种情况下,分治会反复求解那些共子问题,而动态规划将每个子问题的解保存在表格中,从而无需重复计算子问题)
2.钢条切割
问题:给定一段长度为n英寸的钢条和一个价格表 p i ( i = 1 , 2 , 3 , … , n ) p_i(i=1,2,3,…,n) pi(i=1,2,3,…,n),求切割钢条的方案使收益最大
设计动态规划的过程:
-
刻画一个最优子结构的特征–>递归地定义最优解的值–>计算最优解的值(通常使用自底向上的方法)
–>利用计算的信息构造最优解
-
切割方案: 2 n − 1 2^{n-1} 2n−1
在距离钢条左端 i ( i = 1 , 2 , 3 , … , n ) i(i=1,2,3,…,n) i(i=1,2,3,…,n)处,我们总可以选择切割或不切割
-
描述切割“长度为n的钢条”的最大收益 r n r_n rn(刻画一个最优解的结构特征)
设最优解将钢条切割成k段,最优切割方案为: n = i 1 + i 2 + i 3 + … i k n=i_1+i_2+i_3+…i_k n=i1+i2+i3+…ik,
则有最大收益: r n = p i 1 + p i 2 + … + p i n r_n=p_{i_1}+p_{i_2}+…+p_{i_n} rn=pi1+pi2+…+pin
-
利用更短的钢条的最优切割来描述 r n r_n rn: (递归地定义最优解的值)
r n = m a x ( p n , r 1 + r n − 1 , r 2 , r n − 2 , … r n − 1 + r 1 ) r_n=max(p_n,r_1+r_{n-1},r_2,r_{n-2},…r_{n-1}+r_1) rn=max(pn,r1+rn−1,r2,rn−2,…rn−1+r1)
1.第一个参数对应不切割,直接出售长度为n英寸的钢条
2.其他参数对应将钢条切成i和n-i的两端的各个切法
-
最优子结构特征
我们称钢条切割问题满足最优子结构的特征:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解
-
简化 r n r_n rn的递归定义
r n = max 0 ≤ x ≤ n ( p i + r n − i ) r_n=\max_{0 \leq x \leq n} (p_i+r_{n-i}) rn=max0≤x≤n(pi+rn−i)
即钢条的切割方式为:从左边切下长度为i的一段(且不再进行切割),从右边切下长度为n-i的一段,继续进行切割。若没有切割,则第一段长度为n,收益为 p n p_n pn,第二段长度为0,收益为0;
3.自定向下递归实现
int cutRod(int price[], int n){
if(n==0)
return 0;
int max_val = INT_MIN;
for(int i=0;i<n;i++){
max_val =max( max_val,p[i]+cutRod(price,n-i));
}
return max_val;
}
可以看到CUT-ROD反复求解重复子问题,设T(0)=1,有
利用归纳法可以证明 T ( n ) = 2 n T(n)=2^n T(n)=2n
4.使用动态规划求解
两种等价的实现方法:
-
带备忘录的自定向下法
此方法仍然按照自然的递归形式编写,但过程会保存每个子问题的解。当需要一个子问题的解时,过程首先检验是否保存过此解。
-
自底向上法
任何子问题的求解都依赖更小的子问题的求解,当求解某个子问题时,它所依赖的更小子问题都已求解完毕
#include<iostream>
#include <bits/stdc++.h>
#include<math.h>
using namespace std;
// A utility function to get the maximum of two integers
int max(int a, int b) { return (a > b)? a : b;}
/* Returns the best obtainable price for a rod of length n and
price[] as prices of different pieces */
int cutRod(int price[], int n)
{
int val[n+1];
val[0] = 0;
int i, j;
// Build the table val[] in bottom up manner and return the last entry
// from the table
for (i = 1; i<=n; i++)
{
int max_val = INT_MIN;
for (j = 0; j < i; j++)
max_val = max(max_val, price[j] + val[i-j-1]);
val[i] = max_val;
}
return val[n];
}
/* Driver program to test above functions */
int main()
{
int arr[] = {1, 5, 8, 9, 10, 17, 17, 20};
int size = sizeof(arr)/sizeof(arr[0]);
cout <<"Maximum Obtainable Value is "<<cutRod(arr, size);
getchar();
return 0;
}
5.子问题图
我们可以将动态规划方法的子问题图看成自定向下递归树的“收缩版”,所有相同子问题合并为同一节点。