问题描述:已知各个长度的钢条对应的价格;长度为1、2、3、4、5、6、7、8、9、10,对应的价格分别为1、5、8、9、10、17、17、20、24、30;现有一段钢条的长度为n(0<n<11),经过怎样的划分使得它的总价值最大,求出最大价值。
分析:设长度为n的钢条,总价值最大为r(n)=max(p[i]+r(n-i)),其中1<=i<=n;
1、采用Recursive top-down 方法实现如下:
#include<iostream> using namespace std; int cut_rod(int (&p)[11],int n){ if(n==0) return 0; int q=-99999; for(int i=1;i<=n;i++){ int p_rest=cut_rod(p,n-i); q=(q>p[i]+p_rest?q:p[i]+p_rest); } return q; } int main(){ int p[11]={0,1,5,8,9,10,17,17,20,24,30}; cout<<cut_rod(p,7)<<endl; return 0; }
上面这个算法,有很多次重复的递归。比如计算n=4情况,有(p[4]+0), (p[3]+r(1)), (p[2]+r(2)), (p[1]+r(3))四种情况求最大值,需要计算r(3)、r(2)、r(1);这时求(p[1]+r(3))时,需要计算r(3),就又要重复计算(p[1]+r(2)), (p[2]+r(1)), (p[3]+0),即重复计算了r(2)、r(1)。时间复杂度为2^n。
2、采用动态规划的方法
(1)top-down with memorization:
#include<iostream> using namespace std; int memorized_cut_rod_aux(int (&p)[11],int n,int (&r)[11]){ if(r[n]>=0) return r[n]; int q; if(n==0) q=0; else { q=-99999; for(int i=1;i<=n;i++){ int p_rest=memorized_cut_rod_aux(p,n-i,r); q=(q>p[i]+p_rest?q:p[i]+p_rest); } } r[n]=q; return q; } int main(){ int p[11]={0,1,5,8,9,10,17,17,20,24,30}; int r[11]; for(int i=0;i<11;i++) r[i]=-99999; cout<<memorized_cut_rod_aux(p,7,r)<<endl; return 0; }
比如计算r(4),会调用p[1]+r(3), r(3)调用p[1]+r(2), r(2)调用p[1]+r(1)。递归到此,长度n为1,memorized_cut_rod_aux(p,1,r), 会调用最后一个递归memorized_cut_rod_aux(p,0,r),此时n为0,q=0,不再递归调用了,把q=0赋给r[0],递归第一次返回时有r[0]>=0,返回r[0],p[1]+r[0]得到r(1)的最大值、、、总之memorized_cut_rod_aux(p,n,r)的调用顺序是n从n、n-1、n-2、n-3、、、、,则递归到n=0返回时,会逆着记录r[0]、r[1]、
r[2]、、、的值,并使下一次递归的真正执行调用数组r前面已经计算出的所有元素值。
(2)bottom-up method:
#include<iostream> using namespace std; int bottom_up_cut_rod(int (&p)[11],int n){ int r[11]; r[0]=0; int q; for(int i=1;i<=n;i++){ q=-99999; for(int j=1;j<=i;j++) q=(q>p[j]+r[i-j]?q:p[j]+r[i-j]); r[i]=q; } return r[n]; } int main(){ int p[11]={0,1,5,8,9,10,17,17,20,24,30}; cout<<bottom_up_cut_rod(p,7)<<endl; return 0; }
比如现在求n=4,从1、2、3、4遍历,遍历1时,计算p[1]+r[0]值,即为r[1]。记录之后,遍历2,计算p[1]+r[1]和p[2]+r[0],比较出最大值记录为r[2]。记录之后再遍历3,计算p[1]+r[2],p[2]+r[1],
p[3]+r[0],比较出最大值记录为r[3]。同样可以计算出r[4],返回。