200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 《算法导论》15章-动态规划 15.1 钢条切割(含有C++代码)

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

时间:2024-04-17 10:56:20

相关推荐

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

一、引入

动态规划方法通常用来求解最优化问题(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 0q = -∞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 arrayfor 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]//计算所需值qif n==0q = 0else 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] = 0for j = 1 to nq = -∞for i = 1 to jq = max(q,p[i] + r[j-i])//直接访问数组元素来获得子问题的解,r[j-i]一定是最优的r[j] = qreturn 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 arraysr[0] = 0for 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] = qreturn 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;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。