一、引入

动态规划方法通常用来求解最优化问题(optimizationproblem)。这类问题可以有很多可行解,每个解都有一个值,我们希望寻找具有最优值(最小值或最大值)的解。我们称这样的解为问
题的一个最优解(an optimal solution),而不是最优解( the optimal solution),因为可能有多个解都达到最优值。
我们通常按如下4个步骤来设计一个动态规划算法:
1.刻画一个最优解的结构特征。
2.递归地定义最优解的值。
3.计算最优解的值,通常采用自底向上的方法。
4.利用计算出的信息构造一个最优解。

15.1 钢条切割

一、问题相关

钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表p;(i=1, 2,… n),求切割钢条方案,使得销售收益rn最大。注意,如果长度为n英寸的钢条的价格pn足够大,最优解可能就是完全不需要切割。

二、初步想法

1、刻画最优解结构特征

(1)长度为n英寸的钢条共有2n-1种不同的切割方案,因为在距离钢条左端i(i=1,2,… n-1)英寸处,我们总是可以选择切割或不切割。我们用普通的加法符号表示切割方案,因7=2+2+3表示将长度为7英寸的钢条切割为三段一两段长度为2英寸、一段长度为3英寸。
(2)如果一个最优解将钢条切割为k段(对某个1≤k≤n),那么最优切割方案n= i + i2 +…+ ik将钢条切割为长度分别为i1,i2, …,ik的小段,得到最大收益rn= pi1 + pi2 +…+ pik对于上述价格表样例,我们可以观察所有最优收益值r(i=1,2,…,10)及对应的最优切割方案:
r1=1,切割方案1=1(无切割)
r2=5,切割方案2= 2(无切割)
r3=8,切割方案3=3(无切割)
r4=10,切割方案4=2+2
r5=13,切割方案5=2+3
r6=17,切割方案6= 6(无切割)
r7=18,切割方案7=1+6或7=2+2+3
r8=22,切割方案8=2+6
r9=25,切割方案9=3+6
r10= 30,切割方案10= 10(无切割)

(3)更一般地,对于rn(n≥1),我们可以用更短的钢条的最优切割收益来描述它:
rn= max(pn,r1+rn-1,r2+rn-2,…rn-1+r1)
(4)第一个参数pn对应不切割,直接出售长度为n英寸的钢条的方案。其他n-1个参数对应另外n-1种方案:对每个i=1, 2,…,n-1,首先将钢条切割为长度为i和n-i的两段,接着求解这两段的最优切割收益ri和rn-i;(每种方案的最优收益为两段的最优收益之和)。由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的i,选取其中收益最大者。如果直接出售原钢条会获得最大收益,我们当然可以选择不做任何切割。
(5)注意到,为了求解规模为n的原问题,我们先求解形式完全一样,但规模更小的子问题。即当完成首次切割后,我们将两段钢条看成两个独立的钢条切割问题实例。我们通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解。我们称钢条切割问题满足最优子结构(optimal substructure)性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。
(6)一种更简单的办法:我们将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n- i的- 一段继续进行切割(递归求解),对左边的一段则不再进行切割。即问题分解的方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分继续分解的结果。

CUT-ROD(p,q)
if n==0return 0
q = -∞
for i = 1 to nq = max(q,p[i]+CUT-ROD(p,n-i))
return q

仔细一看这个方法并不是特别好,因为钢条一长,不断递归,问题规模巨大…

二、使用动态规划方法求解最优钢条切割问题

动态规划方法是典型的用空间争取时间,典型的时空权衡(time-memory-trade-off)。
有以下几种方法:
第一种方法称为带备忘的自顶向下法(top- down with memoization)曰。此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。我们称这个递归过程是带备忘的(memoized)。因为它“记住”了之前已经计算出的结果。
第二种方法称为自底向上法(bottom up method)。这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的"子问题的求解。因而我们可以将子问题按规模排序,按由小至大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次, 当我们求解它(也是第一次遇到它)时, 它的所有前提子问题都已求解完成。

1、加入备忘机制的伪代码:

自顶向下

MEMORIZED-CUT-ROD(p,n)
let r[0...n] be a new array
for i = 0 to n //新建一个存储的数组r[i] = -∞
return MEMORIZED-CUT-ROD-AUX(p,n,r)
MEMORIZED-CUT-ROD-AUX(p,n,r)
if r[n] >= 0    //检查所需的值是否已知return r[n]
//计算所需值q
if n==0   q = 0
else q = -∞for i = 1 to nq = max(q,p[i]+MEMORIZED-CUT-ROD-AUX(p,n-i,r))
r[n] = q       //将q存入r[n]
return q

自底向上

BOTTOM-UP-CUT-ROD(p,n)
let r[0..n] be a new array  //创建一个新数组保存子问题的解
r[0] = 0
for j = 1 to nq = -∞for i = 1 to jq = max(q,p[i] + r[j-i]) //直接访问数组元素来获得子问题的解,r[j-i]一定是最优的r[j] = q
return r[n]

子问题图

c++代码

自顶而下

#include <iostream>
using namespace std;
int memorizedCutRodAux(int p[], int n, int r[])
{int q;//最大收益值if (r[n] >= 0)return r[n];//检查所需值是否已知if (n == 0)q = 0;//n=0时不会有收益else{q = -1;for (int i = 0; i < n; ++i)q = max(q, p[i] + memorizedCutRodAux(p, n - i - 1, r));}r[n] = q;return q;
}int memorizedCutRod(int p[], int n)
{int *r = new int(n+1);for (int i = 0; i <= n; ++i)r[i] = -1;return memorizedCutRodAux(p, n, r);
}int main()
{int p[10] = { 1,5,8,9,10,17,17,20,24,30 };int q = memorizedCutRod(p, 5);cout << "带备忘的自顶向下方法的最优收益值为:" << q;return 0;
}

自底而上

#include <iostream>
using namespace std;
int BottomUpCutRod(int p[], int n)
{int* r = new int(n + 1);r[0] = 0;if (n == 0) {return 0;}for (int j = 1; j <= n; j++) {int q = INT_MIN;for (int i = 1; i <= j; i++) {q = max(q, p[i-1] + r[j - i]);}r[j] = q;}return r[n];
}
int main()
{int p[10] = { 1,5,8,9,10,17,17,20,24,30 };int q = BottomUpCutRod(p, 4);cout << "带备忘的自底而上方法的最优收益值为:" << q;return 0;
}

2、进阶:不仅返回收益值,还返回第一段长度

EXTENDED-BOTTOM-UP-CUT-ROD(p,n)
let r[0...n] and s[0...n] be new arrays
r[0] = 0
for j = 1 to nq = -∞for i = 1 to jif q < p[i] + r[j-i]q = p[i] + r[j-i]s[j] = i       //将最优长度i保存于s[j]r[j] = q
return r and s

下面的过程接受两个参数:价格表p和钢条长度n,然后调用EXTENDED-BOTTOM-UP-CUT-ROD来计算切割下来的每段钢条的长度s[1…n],最后输出长度为n的钢条的完整的最优切割方案:
PRINT-CUT-ROD-SOLUTION(p,n)

PRINT-CUT-ROD-SOLUTION(p,n)
(r,s)= EXTENDED-BOTTOM-UP-CUT-ROD(p,n)
while n> 0print s[n]n=n-s[n]

C++代码

#include <iostream>using namespace std;void extendedBottomUpCutRod(int p[], int n, int r[], int s[])
{//int r[n+1];记录不同规模子问题的解,这里是1~10//int s[n+1];记录切割的解,即怎样切的int q;//记录收益r[0] = 0;for (int j = 1; j <= n; ++j){q = -1;//初始为负,常见的表示未知数的方法for (int i = 1; i <= j; ++i){if (q < p[i - 1] + r[j - i]){q = p[i - 1] + r[j - i];s[j] = i;}}r[j] = q;}
}void printCutRodSolution(int p[], int n, int r[], int s[])
{extendedBottomUpCutRod(p, n, r, s);cout << "长度为" << n << "的钢条按照此种切割方法的最优收益为:" << r[n] << endl;cout << "对应的最优切割方案的解为:";while (n > 0){cout << s[n] << " ";//输出最优切割方案n = n - s[n];}
}int main()
{int p[10] = { 1,5,8,9,10,17,17,20,24,30 };int r[11], s[11];printCutRodSolution(p, 10, r, s);return 0;
}

《算法导论》15章-动态规划 15.1 钢条切割(含有C++代码)相关推荐

  1. 算法复习——动态规划篇之钢条切割问题

    算法复习--动态规划篇之钢条切割问题 以下内容主要参考中国大学MOOC<算法设计与分析>,墙裂推荐希望入门算法的童鞋学习! 1. 问题背景 钢铁切割: ​ 现有一段长度为10的钢条,可以零 ...

  2. 《算法导论》中动态规划求解钢条切割问题

    动态规划算法概述 动态规划(dynamic programming)1是一种与分治方法很像的方法,都是通过组合子问题的解来求解原问题.不同之处在于,动态规划用于子问题重叠的情况,比如我们学过的斐波那契 ...

  3. 我眼中的算法导论 | 第一章——算法在计算中的作用、第二章——算法基础

    一个小白的算法学习之路.读<算法导论>第一天.本文仅作为学习的心得记录. 算法(Algorithm) 对于一个程序员来说,无论资历深浅,对算法一词的含义一定会或多或少有自己的体会,在< ...

  4. 算法导论2-9章补充几道题

    本篇博文意在对前几章中遗漏的,本人觉得有意思的习题当独拿出来练练手. 1.习题2-4,求逆序对,时间复杂度要求Θ(nlgn) 定义:对于一个有n个不同的数组A, 当i<j时,存在A[i]> ...

  5. 算法导论第二章部分习题自我解答

    练习: 2.3-2:去掉合并排序中的哨兵值 #include <iostream> using namespace std; void Merge(int* A, int p, int q ...

  6. 算法图解第九章动态规划读书笔记

    动态规划是一种解决棘手问题的方法,它将问题分成小问题,并先着手解决这些小问题 一.背包问题:一个背包的容量是4磅,吉他容量一磅,价值1500美元,音响容量4磅,价值3000美元,笔记本电脑容量3磅,价 ...

  7. 算法导论22章 基本图算法习题

    22.1-6  给出O(V)时间算法判断有向图G是否存在一个通用汇点(universal sink).通用汇点指的是入度为|V|-1,出度为0的节点. 思路: 考虑图的邻接矩阵A,假设i为通用汇点,则 ...

  8. 算法导论第九章习题9.2-3

    使用迭代版本的随机选择函数实现选择第i小元素.原理与9.2例题相同,该代码为9.2例题的迭代版实现.具体代码如下: #include<iostream> using namespace s ...

  9. 算法导论-动态规划(钢条切割问题)

    写下文章来记录下自己学习算法导论的笔记 文章目录 写下文章来记录下自己学习算法导论的笔记 动态规划的目的 设计动态规划算法 钢条切割问题 问题描述 刻画问题结构(建立方程) 递归方程建立 带备忘录的自 ...

最新文章

  1. VS2010 MFC中改变static字体颜色、大小、背景颜色(自定义类),及手动关联变量的方法...
  2. 代码实现中文命名实体识别(包括多种模型:HMM,CRF,BiLSTM,BiLSTM+CRF)
  3. RxSwift之深入解析如何创建Observable序列
  4. 7种常用p2p共享协议
  5. C#下实现的K-Means优化[1]-「离群点检测」
  6. (转)淘淘商城系列——SSM框架整合之Service层整合
  7. C++中依赖受限名称定义编译无法通过的问题
  8. php date( ymd_PHP DATE()
  9. ~~spfa判断图中是否存在负环
  10. MySQL 中的共享表空间与独立表空间,用哪个好呢?
  11. self计算机语言,python中self在函数中如何使用
  12. 基础知识——常见文件格式
  13. MyBatis集合Spring(三)之mapper
  14. Exchange2010 server的部署及配置(一)
  15. 80端口和443端口是什么?有什么区别?
  16. 居家办公提高工作效率的八点建议
  17. 俄亥俄州立大学宣布开放 Swift 编程和 App 开发课程
  18. OpenVINO系列19_face_detection检测人脸并做标记
  19. python获取鼠标选取的内容_Python三维可视化:鼠标选取交互操作
  20. 【模型分享】SolidWorks摇摆轮机构运动仿真

热门文章

  1. CSS 字体样式大全
  2. react-router嵌套路由页面无法显示
  3. 基于linux2.6.30.4内核的DM9000网卡驱动编译成模块成功ping通
  4. 刘世光 计算机图像学,刘世光
  5. java 制作 winrar,windows 上java调用winrar压缩文件为rar 格式
  6. [Tensorflow]L2正则化和collection【tf.GraphKeys】
  7. wechat-0051,微信公众号,第三方登录—扫码绑定
  8. 怎么设置计算机网络打印机共享,打印机共享设置,告诉你打印机共享怎么设置...
  9. 函数极限什么时候可以拆分
  10. 英飞凌 DAVE™ 4.1.2 SDK 开发app学习笔记——什么是DAVE APP?