算法导论-第15章-动态规划-15.1 钢条切割问题
2014-11-07 08:31
477 查看
一、综述
动态规划是通过组合子问题的解而解决整个问题的。
动态规划对每个子问题只求解一次,将其结果保存在一张表中。
动态规划通常用于最优化问题。
动态规划的设计步骤:
a. 描述最优解的结构
b. 递归定义最优解的值
c. 按自底向上的方式计算最优解的值
d. 由计算出的信息构造一个最优解
动态规划方法对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果,而不必重新计算。因此,动态规划方法是付出额外的内存空间来节省计算时间,是典型的时空权衡的例子。时间上的节省是非常巨大的:可能将一个指数时间的解转化为一个多项式时间的解。
动态规划有两种等价的实现方法
1、带备忘的自顶向下法。
此方法仍按自然的递归形式编写过程,但过程会保存每个子问题的解(通常保存在一个数组或散列表中)。当需要一个子问题的解时,过程首先检查是否已经保存过此解。如果是,则直接返回保存的值,从而节省了计算时间;否则,按通常方式计算这个子问题。
2、自底向上法。
这种方法一般需要恰当定义子问题“规模”的概念,使得任何子问题的求解都只依赖于“更小的”子问题求解。因而我们可以将子问题按规模排序,按从小到大的顺序进行求解。当求解某个子问题时,它所依赖的那些更小的子问题都已求解完毕,结果已经保存。每个子问题只需求解一次,当我们求解它时,它的所有前提子问题都已求解完毕。
通常情况下,如果每个子问题都必须至少求解一次,自底向上法会比自顶向下法快,因为自底向上法没有递归调用的开销,表的维护开销也更小。如果子问题空间中的某些子问题完全不必求解,备忘法就体现出优势了,因为它只会求解那些绝对必要的子问题。
二、代码
15.1 钢条切割假设公司出售一段长度为i英寸的钢条的价格为Pi(i = 1, 2, ...单位:美元),下面给出了价格表样例:
长度i 1 2 3 4 5 6 7 8 9 10
价格Pi 1 5 8 9 10 17 17 20 24 30
切割钢条的问题是这样的:给定一段长度为n英寸的钢条和一个价格表Pi,求切割方案,使得销售收益Rn最大。
当然,如果长度为n英寸的钢条价格Pn足够大,最优解可能就是完全不需要切割。
对于上述价格表样例,我们可以观察所有最优收益值Ri及对应的最优解方案:
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(无切割)
更一般地,对于Rn(n >= 1),我们可以用更短的钢条的最优切割收益来描述它:
Rn = max(Pn, R1 + Rn-1, R2 + Rn-2,...,Rn-1 + R1)
首先将钢条切割为长度为i和n - i两段,接着求解这两段的最优切割收益Ri和Rn - i(每种方案的最优收益为两段的最优收益之和),由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的i,选取其中收益最大者。如果直接出售原钢条会获得最大收益,我们当然可以选择不做任何切割。
思路:先将钢条切成两条,有n-1种方案,每一种方案的最优解都等于两个子钢条的最优解。我们从这n-1个伪最优解再挑出最优的解了。
自顶向下递归实现的伪代码:
CUT-ROD(p,n)
if n == 0
return 0
q=负无穷
for i = 1 to n
q=max(q,p[i]+CUT-ROD(p,n-i))
return q
C++代码
#include<iostream> using namespace std; #define NIL (-0x7fffffff-1) int max(int a,int b) { if(a>=b) return a; else return b; } int cut_rod(int *p,int n) { if(n==0) return 0; int q=NIL; if(n<=10) { for (int i=0;i<n;i++) { q=max(q,p[i]+cut_rod(p,n-1-i)); } return q; } else if(n>10) { int b=n/10; n=n-b*10; if(n==0) q=0; for (int i=0;i<n;i++) { q=max(q,p[i]+cut_rod(p,n-1-i));; } return q+b*30; } } int main() { int p[]={1,5,8,9,10,17,17,20,24,30}; int n; cout<<"Please input a int number: "; cin>>n; int result=cut_rod(p,n); cout<<result<<endl; return 0; }
上面只用了分治策略,这个算法的性能很差,是指数次的,递归调用次数T(n)=2^n,因为在子问题的求解中很多都是重复的。
下面介绍动态规划法
1、带备忘的自顶向下法
EMOIZED-CUT-ROD(p,n) let r[0..n] be a new array for i = 0 to n //记录长度为n时已经求得的受益最大的结果 return MEM-CUT-ROD-AUX(p,n,r) MEMOIZED-CUT-ROD-AUX(p,n,r) if r >=0 return r if n==0 q=0 else q=负无穷 for i=1 to n q=max(q,p[i]+MEM-CUT-ROD-AUX(p,n-i,r)) r =q return q
C++代码
#include<iostream> using namespace std; #define NIL -10000 int max(int a,int b) { if(a>=b) return a; else return b; } int memoized_cut_rod_aux(int *p,int n,int *r) { int q=NIL; if(r >=0) return r ; if(n==0) q=0; else { q=NIL; if(n<=10) { for (int i=0;i<n;i++) { q=max(q,p[i]+memoized_cut_rod_aux(p,n-1-i,r)); } r =q; return q; } else if(n>10) { int b=n/10; n=n-b*10; if(n==0) q=0; for (int i=0;i<n;i++) { q=max(q,p[i]+memoized_cut_rod_aux(p,n-1-i,r)); } r =q+b*30; return q+b*30; } } return 0; } int memoized_cut_rod(int *p,int n) { int *r; r=(int *)malloc(sizeof(int)*(n+1)); for (int i=0;i<n;i++) { r[i]=NIL; } return memoized_cut_rod_aux(p,n,r); } int main() { int p[]={1,5,8,9,10,17,17,20,24,30}; int n; cout<<"Please input a int number: "; cin>>n; int result=memoized_cut_rod(p,n); cout<<result<<endl; return 0; }
2、自底向上版本
//从小到大依次求出每种长度的受益的最优解
BOTTOM-UP-CUT-ROD(p,n)
let r[0..n] be a new array
r[0]=0
for j=1 to n
q=负无穷
for i=1 to j
q=max(q,p[i]+r[j-i])
r[j]=q
return r
C++代码
//自底向上的方法,这里面的n不能超过11,受P限制
#include<iostream>
using namespace std;
#define NIL -10000
int max(int a,int b)
{
if(a>=b)
return a;
else
return b;
}
int bottom_cut_rod(int *p,int n)
{
int *r;
r=(int *)malloc(sizeof(int)*(n+1));
r[0]=0;
for (int j=0;j<n;j++)
{
int q=NIL;
for(int i=0;i<=j;i++)
{
q=max(q,p[i]+r[j-i]);
}
r[j+1]=q;
}
return r
;
}
int main()
{
int p[]={1,5,8,9,10,17,17,20,24,30};
int n;
cout<<"Please input a int number: ";
cin>>n;
cout<<bottom_cut_rod(p,n);
cout<<endl;
return 0;
}3、下面的伪代码还保留了切割长度
EXTEND-BOTTOM-UP-CUT-ROD(p,n) let r[0..n] and s[0..n] be new arrays r[0]=0 for j = 1 to n q=负无穷 for i =1 to j if q < p[i]+r[j-i] q=p[i]+r[j-i] s[j]=i r[j]=q return r and s
4、 打印出切割长度
PRINT-CUT-ROD-SOLUTION(p,n) (r,s)=EXTEND-BOTTOM-UP-CUT-ROD(p,n) while n >0 print s n=n-s
C++代码
#include<iostream> using namespace std; #define NIL -10000 void extern_bottom_up_cut_rod(int *p,int n,int *r,int *s) { r[0]=0; s[0]=0; for (int j=0;j<n;j++) { int q=NIL; for (int i=0;i<=j;i++) { if(q<(p[i]+r[j-i])) { q=p[i]+r[j-i]; s[j+1]=i+1; r[j+1]=q; } } } } void print_cut_rod_solution(int *s,int n) { while (n>0) { cout<<s <<'\t'; n=n-s ; } } int main() { int p[]={1,5,8,9,10,17,17,20,24,30}; int n; int *r; int *s; cout<<"Please input a int number: "; cin>>n; r=(int *)malloc(sizeof(int)*(n+1));//存放n时的最大受益 s=(int *)malloc(sizeof(int)*(n+1));//存放n时的切割长度 extern_bottom_up_cut_rod(p,n,r,s); cout<<r <<endl;//最大受益 cout<<"切割的长度分别是: "<<endl; print_cut_rod_solution(s,n); cout<<endl; free(r); free(s); return 0; }
相关文章推荐
- 动态规划之钢条切割问题自底向上发的实现(算法导论第15章)
- 算法导论 第15章 动态规划:15.1钢条切割
- (动态规划) 算法导论_钢条切割问题.(补)
- 【算法导论学习-27】动态规划经典问题01:钢条切割的最大收益
- 算法实践篇-钢条切割问题-动态规划
- 算法导论——钢条切割问题
- 【算法导论】动态规划之“钢管切割”问题
- 钢条切割--动态规划--算法导论
- 算法导论-第15章-动态规划:钢条切割问题自底向下方法C++实现
- java,动态规划,算法导论之钢条切割(O(n)时间渐进性)
- [算法学习笔记]动态规划之钢条切割问题
- 算法导论笔记——钢条切割问题
- 【算法设计-动态规划】钢条切割问题
- 算法导论——15动态规划——15.1钢条切割
- 【算法导论】动态规划之“钢管切割”问题
- 动态规划之钢条切割问题
- 动态规划之钢条切割(算法导论)
- 动态规划解决钢条切割问题
- 动态规划之钢条切割(算法导论)
- 算法导论15章 动态规划之矩阵链乘法问题