本人在学习《算法导论》的过程中,对于动态规划这部分的内容不是特别理解,于是决定做一下学习与解决记录,欢迎讨论交流。
0- 动态规划问题的一般步骤
1- 刻画一个最优解的结构特征
2- 递归定义最优解的值
3- 计算最优解的值,通常采用自底向上的方法
4- 利用计算出的信息构造一个最优解
1- 问题描述
Serling 公司购买长钢条,将其切割为锻钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。
假定我们知道Serling公司出售一段长度为i英寸的钢条的价格为pi(i = 1, 2,…,单位为美元)。钢条的长度均为整英寸。下表给出了一个价格表的样例。
长度i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格p(i) | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
2-问题分析
钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表p(i)(i = 1, 2,…,n),求切割钢条方案,使得销售收益rn最大。注意如果长度为n英寸的钢条价格p(n)足够大,最优解可能是完全不需要切割。
考虑n=4的情况。下图给出了4英寸钢条的所有可能切割方案,包括根本不切割的方案。从下图可以看出,将一段长度为4英寸的钢条切割为两段各长为2英寸的钢条,将产生p(2)+p(2)=5+5=10的收益,为最优解。
对于最优收益rn(n≥1)可以用更短的钢条的最优收益来描述:
第一个参数pn对应不切割,直接出售长度为n英寸的钢条的方案。其中n-1个参数对应另外n-1种方案:对每个i = 1, 2, …, n-1,首先将钢条切割为i和n-i的两段,接着求解这两段的最优切割收益ri和rn -i(每种方案的最优收益为两段的最优收益之和)。由于无法预知那种方案的将获得最佳收益,所以必须考虑所有可能的i,选取其中的收益最大者。如果直接出售圆钢条会获得最大收益,当然可以不做任何切割。
可以看出,通过组合两个子问题(完成首次切割之后,即将问题转化为求解两个独立的钢条切割问题实例)的最优解,并在所有可能得两段切割方案中选取组合收益最大者,构成原问题的最优解。
钢条问题是满足最优子结构的(optimal substructure) 性质的:问题的最优解由相关子问题的最优解组合而成,而且这些子问题可以独立求解。
3-自顶向下递归实现
简单的递归求解方法: 将钢条从左边切割下长度为i的一段,只对右边剩余的长度为n-i的一段继续进行切割(递归求解),对左边的一段则不再进行切割。问题的分解方式为:将长度为n的钢条分解为左边的开始一段,以及剩余部分继续分解的结果。这样,不做任何切割的方案可以描述为:第一段的长度为你n,收益为pn,剩余部分的长度为0,对应的收益r0=0。 于是可以得到如下公式:
原问题的最优解只包含一个相关子问题(右边剩余部分)的解。下面给出朴素递归算法的解:
#include <iostream>
#include <vector>
#include <limits.h>
#include <algorithm>
using namespace std;
vector<int> price{
1, 5, 8, 9, 10, 17, 17, 20, 24, 30};//钢条对应的价格
int CutRod(vector<int> &price, int length){
if(length == 0) return 0;//如果钢条长度为0,直接返回
int maxvalue = INT_MIN;//因为钢条价格为正值,所以首先默认maxvalue为最小值
for(int i = 1; i <= length; ++i){
//取前一次迭代算得的maxvalue与本次迭代结果的最大值
//迭代:当前钢条段的价值 + 剩余右边钢条的价值
//(这里因为下标从0开始,所以有-1)
maxvalue = max(maxvalue, price[i - 1] + CutRod(price, length - i));
}