#动态规划(一)
算法导论动态规划——钢条切割
- **Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。图15-1给出了一个价格表的样例。
钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表pi{p_i}pi(i=1,2,…n),求切割钢条方案,使得销售收益rn{r_n}rn最大。注意,如果长度为n英寸的钢条的价格pn{p_n}pn足够大,最优解可能就是完全不需要切割。
问题分析
分割钢条的所有方案共有2n−12^{n-1}2n−1(长度为n的钢条,可切割点有n-1个,可以选择切或不切)
- 假设最佳方案为将钢条切割为k段,分别为i1{i_1}i1,i2{i_2}i2,···ik{i_k}ik;对于各段得到的最大收益分别为人r1{r_1}r1,r2{r_2}r2,···rk{r_k}rk;
- 计算ri是小于规模n的新的子问题,且r1{r_1}r1,r2{r_2}r2,···rk{r_k}rk,可能有相互重叠部分。
- 这个题可以用深搜做,遍历所有方案做比较,时间效率为指数级。
- 若用动态规划做:分析样例表:
- n=1时,r1{r_1}r1=1;
- n=2时,r2{r_2}r2=5;
- n=3时,r3{r_3}r3=8;
- n=4时,r4{r_4}r4=r2{r_2}r2+r2{r_2}r2=10;
- n=5时,r5{r_5}r5=r2{r_2}r2+r3{r_3}r3=13;
- n=6时,r6{r_6}r6=17;
- n=7时,r7{r_7}r7=r2{r_2}r2+r5{r_5}r5=r2{r_2}r2+r2{r_2}r2+r3{r_3}r3=18
- n=8时,r8{r_8}r8=r3{r_3}r3+r5{r_5}r5=21
- n=9时,r9{r_9}r9=r3{r_3}r3+r6{r_6}r6=r4{r_4}r4+r5{r_5}r5=25
- n=10时,r10{r_{10}}r10=30;
方法一:选择第一个切割点:
rn{r_n}rn=max(pn{p_n}pn,r1{r_1}r1+rn−1{r_{n-1}}rn−1,···ri{r_i}ri+rn−i{r_{n-i}}rn−i)i>=1&&i<=n/2;
代码块(一)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int p[1001];
int r[1001];
int N;
int main() {
memset(r,-1,sizeof(r));
cin>>N;
for(int i =1; i<=N; i++) {
cin>>p[i];
}
int CutRod(int n);
int ans;
ans=CutRod(N);
cout<<ans<<endl;
}
int CutRod(int n) {
if(n==1)return p[n];
int m=p[n];
for(int i =1; i<=n/2; i++) {
m=max(m,CutRod(i)+CutRod(n-i));
}
return m;
}
方法二:将钢条从左边切割下长度为i的一段,只对右边剩下的长度为n-i的一段继续进行切割,对左边的一段则不再进行切割。这样得到的公式为:。这样原问题的最优解只包含一个相关子问题(右端剩余部分)的解,而不是两个。:
rn{r_n}rn=max(pi{p_i}pi+rn−i{r_{n-i}}rn−i),其中i>=n/2&&i<=n;
当i=n时,rn{r_n}rn=(pn{p_n}pn+r0{r_{0}}r0)=pn{p_n}pn,即对长度为n的钢条不切割的状态。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int p[1001];
int r[1001];
int N;
int main() {
int CutRod2(int n);
memset(r,-1,sizeof(r));
cin>>N;
for(int i =1; i<=N; i++) {
cin>>p[i];
}
int ans;
ans=CutRod2(N);
cout<<ans<<endl;
}
int CutRod2(int n) {
int m=p[n];
if(n==0)return 0;
else if(n==1)return p[n];
else {
for(int i =n/2; i<=n; i++) {//因为n=1时,n/2==0,p[0]越界,所以,n==1要单独拿出来。
int t=p[i]+CutRod2(n-i);
if(t>m)
m=t;
}
return m;
}
}
方法三:在递归过程中涉及到很多次的重复递归计算,可以将曾经计算过的数据保存到数组,后续直接从数组中找,没有再计算。下面在方法二的基础上加入记忆数组。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int p[1001];
int r[1001];
int N;
int memory[1001];
int main() {
int CutRod2(int n);
memset(r,-1,sizeof(r));
cin>>N;
for(int i =1; i<=N; i++) {
cin>>p[i];
}
int ans;
memset(memory,-1,sizeof(memory));//对memory数组初始化;因为收益不为负数,所以初始化为-1
memory[0]=0 ;
memory[1]=p[1];
ans=CutRod2(N);
cout<<ans<<endl;
}
int CutRod3(int n) {
if(memory[n]>=0)return memory[n];
else {
for(int i =n/2; i<=n; i++) {
//因为n=1时,n/2==0,p[0]中没有存有效数据,所以,n==1的情况要单独拿出来。
int t=p[i]+CutRod2(n-i);
if(t>memory[n])
memory[n]=t;
}
//memory数组保存中间计算结果。且结果一旦保存,不会再发生改变。
return memory[n];
}
}
重构解(即保存切割策略)
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
int p[1001];
int r[1001];
int N;
int memory[1001];
int main() {
int CutRod3(int n);
memset(r,-1,sizeof(r));
cin>>N;
for(int i =1; i<=N; i++) {
cin>>p[i];
}
int ans;
memset(memory,-1,sizeof(memory));//对memory数组初始化因为收益不为负数,所以初始化为-1
memory[0]=0 ;
memory[1]=p[1];
r[1]=1;//对r[1]直接赋值
ans=CutRod3(N);
cout<<"最大收益为:"<<ans<<endl;
cout<<"可最大收益的策略为:";
while(N>0&&r[N]>0) {
cout<<r[N]<<" ";
N-=r[N];
}
}
int CutRod3(int n) {
if(memory[n]>=0)return memory[n];
else {
for(int i =n/2; i<=n; i++) {
//因为n=1时,n/2==0,p[0]中没有存有效数据,所以,n==1要单独拿出来。
int t=p[i]+CutRod3(n-i);
if(t>=memory[n]) {//当N=7时,t>memory[n] 输出方案为3 2 2
memory[n]=t; //t>=memory[n] 输出方案为 6 1
r[n]=i;
}
}
//memory数组保存中间计算结果。且结果一旦保存,不会再发生改变。
return memory[n];
}
}
方法一、二、三、及重构解均为自顶向下设计算法的。下面分析如何自底向上设计算法:
由子问题图可以发现,上层问题只依赖于比自己低的子问题。
#include<iostream>
using namespace std;
int p[1001];
int memory[1001];
int r[1001];
int N;
int main() {
cin>>N;
for(int i =1; i<=N; i++) {
cin>>p[i];
}
memory[0]=0;
for(int i=1; i<=N; i++) {
int max=p[i];//假设当前不切割为最大收益
int length=i;//假设当前不切割为最大收益的,策略长度为i
for(int j =1; j<i; j++) {
int t=memory[j]+memory[i-j];//由图可知,规模为i的问题,只与所有规模为j(j<i)的问题有关联。
if(max<t) {
max=t;
length=j;
}
}
memory[i]=max;
r[i]=length;
}
cout<<"最大收益为:"<<memory[N]<<endl;
cout<<"获得最大收益的策略是:";
while(N>0&&r[N]>0) {
cout<<r[N]<<" ";
N-=r[N];
}
}