动态规划是我们耳熟能详的基本算法。动态规划是全局状态下的最优解,不同于贪心算法。贪心算法是每次找出当前状态下的最优解,但是全局并不一定是最优解。接下来我们通过《算法导论》书上的一个经典例题来完成对动态规划的学习——钢条切割问题。
问题描述: Serling公司购买长钢条,将其切割为短钢条出售。切割工艺本身没有成本支出。公司管理层希望知道最佳的切割方案。
价格表用例:
长度 i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
价格 p | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
分析:此问题的求解就是把一定长度的钢条分为两段,然后再分别求解每段所带来的价值。对此,我们可以简化一下。首先规定住一定的长度的钢条,不断求解剩余部分,也就是说把求解两部分转化为不断求解一部分,这样想来可以利用递归来实现。递归版本的伪代码为:
1 cutRod(p, n){ 2 if(n == 0) 3 return 0; 4 q = -oo; 5 for(I = 0; I <= n; ++i){ 6 q = max(q, p[i] + cutRod(p, n-i)); 7 } 8 return q; 9 }
递归的时间复杂度不能够忍受,小规模还好,大规模运行时间太长。但是递归未尝不是一个方法。在这里我们引出我们今天的主角——动态规划。动态规划有两个版本:自底向上和自上而 下。自底向上的动态规划容易理解,我们来学习一下。核心思想是采取一个记忆数组存放每段长度的最优解,并以此作为求解大数值的最优解。也就是说,先求出长度为1的最优解,再求出 2......等求解大数值时,分解出的各类小数值已经知晓可以直接拿来调用。这其中,记忆数组尤为重要,也比较不容易理解。C++版本的代码如下:
1 #include <iostream> 2 using namespace std; 3 4 int temp[100]; 5 int calculate(int *price, int n){ 6 temp[0] = 0; 7 int a; 8 for(int i = 1; i <= n; ++i) { 9 a = -128; 10 for(int j = 1; j <= i; ++j){ 11 if(a < price[j] + temp[i - j]){ 12 a = price[j] + temp[i - j]; 13 } 14 } 15 temp[i] = a; 16 } 17 return temp[n]; 18 } 19 20 int main() { 21 int p[11] = {0,1,5,8,9,10,17,17,20,24,30}; 22 int n = 10; 23 cout<<calculate(p, n)<<endl; 24 25 return 0; 26 }
PS:限于知识有限,言语中定有疏忽不能发现,希望读者能够不吝赐教,万分感激!