概述:
15.1钢条切割
钢条切割问题:给定定长的钢条和价格表,求切割方案,使得收益最大。如果n英寸的钢条的价格足够大,则不需要切割。
代码如下:
//朴素递归求解钢条切割收益,由于递归过程中反复求解同一个子问题,所以导致递归次数呈指数级增长,运行时间也过长。
#include <iostream>
using namespace std;
int Max(int a,int b)
{
return a>b?a:b;
}
int CUT_ROD(int p[],int n)
{
if (n==0)
{
return 0;
}
int q=-0x7fffffff;
for (int i=0;i<n;i++)
{
q=Max(q,p[i]+CUT_ROD(p,n-i-1));
}
return q;
}
void main()
{
const int n=10;
int p[n]={1,5,8,9,10,17,17,20,24,30};
cout<<CUT_ROD(p,4);
}
使用动态规划方法求解最优钢条切割问题
动态规划有两种等价实现方式:
① 带备忘的自顶向下法,用自然的递归方式,但是过程会保存每个子问题的解。节省了递归时间。
②自底向上法,需要恰当定义子问题“规模”,使其从小到大顺序求解。求解问题时,其子问题总是先解决了。
代码如下:
//下面在求解钢条切割过程中,可能重复求解的子问题都保存起来,使所有子问题仅求解一次,这样运行时间会变为多项式时间。
#if 0
#include <iostream>
using namespace std;
int Max(int a,int b)
{
return a>b?a:b;
}
int MEMOIZED_CUT_ROD_AUX(int p[],int n,int r[])
{
static int q;
if (r[n]>=0)
{//保存已求过的值。
return r[n];
}
if (n==0)
{
q=0;
}
else
{
q=-0x7fffffff;
for (int i=0;i<n;i++)
{
q=Max(q,p[i]+MEMOIZED_CUT_ROD_AUX(p,n-i-1,r));
}
}
r[n]=q;
return q;
}
int MEMOIZED_CUT_ROD(int p[],int n)
{//方法①自顶向下的动态规划方案
int *r=new int[n];
for (int i=0;i<n;i++)
{
r[i]=-0x7fffffff;
}
return MEMOIZED_CUT_ROD_AUX(p,n,r);
}
int BOTTOM_UP_CUT_ROD(int p[],int n)
{//方法②自底向下的动态规划方案
int *r=new int[n];
r[0]=0;
for (int j=0;j<n;j++)
{
int q=-0x7fffffff;
//int q=p[j+1];
for (int i=0;i<=j;i++)
{
q=Max(q,p[i]+r[j-i]);
}
r[j+1]=q;
}
return r[n];
}
void main()
{
const int n=10;
int p[10]={1,5,8,9,10,17,17,20,24,30};
cout<<MEMOIZED_CUT_ROD(p,9)<<endl;
cout<<BOTTOM_UP_CUT_ROD(p,9)<<endl;
}
子问题图
子问题图规模可以确定动态规划算法的运行时间。其运行时间与图的顶点和边的数量呈线性关系。
重构解:对于钢条切割方案,我们上文仅求最大收益,没有返回对应方案,所以下面代码是既返回收益,同时也返回方案。
//既返回收益,也返回最佳方案。
#include <iostream>
using namespace std;
struct array
{
int r;//代表最大收益
int s;//代表切割方案
};
struct array *EXTENDED_BOTTOM_UP_CUT_ROD(int p[],int n)
{
struct array *rs=new struct array[n];
rs[0].r=0;
int q;
for (int j=0;j<n;j++)
{
q=-0x7fffffff;
//int q=p[j+1];
for (int i=0;i<=j;i++)
{
if (q<p[i]+rs[j-i].r)
{
q=p[i]+rs[j-i].r;
rs[j+1].s=i+1;
}
}
rs[j+1].r=q;
}
return rs+n;
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
struct array *rs=EXTENDED_BOTTOM_UP_CUT_ROD(p,n);
while (n>0)
{
cout<<(*rs).s<<" ";
n=n-(*rs).s;
rs=rs-(*rs).s;
}
}
void main()
{
const int n=10;
int p[10]={1,5,8,9,10,17,17,20,24,30};
cout<<(*EXTENDED_BOTTOM_UP_CUT_ROD(p,4)).r<<endl;
PRINT_CUT_ROD_SOLUTION(p,4);
}
练习:
15.1-1由公式(15.3)和初始条件T(0)=1,证明公式(15.4)成立。
T(n)=1+T(0)+T(1)+...T(n-1) 则有T(n-1)=1+T(0)+T(1)+....+T(n-2) 所以T(n)=2*T(n-1) ,所以根据这个递归式有:(T(1)/T(0))*(T(2)/T(1))........T(n)/T(n-1)=T(n)/T(0)=2^n 因为T(0)=1
所以T(n)=2^n成立。
15.1-2 举反例证明下面的“贪心”策略不能保证总是得到最优切割方案。定义长度为i的钢条的密度pi/i,即每英寸的价值。贪心策略将长度为n的钢条切割下长度为i(1<i<n)的一段,其密度最高。接下来继续使用相同的策略切割长度为n-i的剩余部分。
n=1 密度为1,n=2,密度为2.5 ,n=3 密度为2.7 n=4 密度为2.25 那么如果我们有一条4英寸的钢条,从密度最高处切割,那么就是从n=3处切割,收益为8,剩余的部分为n=1,收益为1,总收益为8+1=9. 但是如果从n=2处切割呢,两段都为2英寸,那么总收益就是5+5=10>9 但是这不是密度最高处。所以题目所给方案不能确定最大收益。
15.1-3 我们对钢条切割问题进行一点修改,除了切割下的钢条段具有不同价格pi外,每次切割还要付出固定的成本c.这样,切割方案的收益就等于钢条段价格之和减去切割的成本。设计一个动态规划算法解决修改后的钢条切割问题。
代码如下:此代码还记录的最优方案解
//15.1-3带有固定切割成本的钢条切割方案
#if 0
#include <iostream>
using namespace std;
int Max(int a,int b)
{
return a>b?a:b;
}
struct array
{
int r;//代表最大收益
int s;//代表切割方案
};
struct array *EXTENDED_BOTTOM_UP_CUT_ROD(int p[],int n)
{
struct array *rs=new struct array[n];
rs[0].r=0;
int q;
for (int j=0;j<n;j++)
{
int flag=1;//哨兵为1代表无切割。
q=p[j];
for (int i=0;i<j;i++)
{
//q=Max(q,p[i]+rs[j-i].r-1);
if(q<=p[i]+rs[j-i].r-1)//切割固定成本c=1
{
q=p[i]+rs[j-i].r-1;
rs[j+1].s=i+1;
flag=0;//哨兵为0代表有切割。
}
}
if (j==i)//i=j代表无切割
{
if (q<=p[i]+rs[j-i].r&&flag)
{//无切割时注意切割方案就等于钢条长度。
rs[j+1].s=i+1;
}
}
rs[j+1].r=q;
}
return rs+n;
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
struct array *rs=EXTENDED_BOTTOM_UP_CUT_ROD(p,n);
while (n>0)
{
cout<<(*rs).s<<" ";
n=n-(*rs).s;
rs=rs-(*rs).s;
}
}
void main()
{
const int n=10;
int p[10]={1,5,8,9,10,17,17,20,24,30};
cout<<(*EXTENDED_BOTTOM_UP_CUT_ROD(p,10)).r<<endl;
PRINT_CUT_ROD_SOLUTION(p,10);
}
15.1-4 修改MEMOIZED-CUT-ROD,使之不仅返回最优收益值,还返回切割方案。
//15.1-4动态规划法递归求解最优钢条切割问题。不仅返回收益还返回切割方案。
#include <iostream>
using namespace std;
struct array
{
int r;//代表最大收益
int s;//代表切割方案
};
int Max(int a,int b)
{
return a>b?a:b;
}
struct array *MEMOIZED_CUT_ROD_AUX(int p[],int n,struct array rs[])
{
static int q;
if (rs[n].r>=0)
{
return rs+n;
}
if (n==0)
{
q=0;
}
else
{
q=-0x7fffffff;
for (int i=0;i<n;i++)
{
int t=p[i]+MEMOIZED_CUT_ROD_AUX(p,n-i-1,rs)->r;
if (q<t)
{
q=t;
rs[n].s=i+1;//n相当于上面迭代里的j+1,i+1和上面迭代一样。
}
}
}
rs[n].r=q;
return rs+n;
}
struct array *MEMOIZED_CUT_ROD(int p[],int n)
{
struct array *rs=new struct array [n];
for (int i=0;i<=n;i++)
{
rs[i].r=-0x7fffffff;
}
return MEMOIZED_CUT_ROD_AUX(p,n,rs);
}
void PRINT_CUT_ROD_SOLUTION(int p[],int n)
{
struct array *rs=MEMOIZED_CUT_ROD(p,n);
cout<<"最大收益:"<<rs->r<<endl;
cout<<"切割方案:";
while (n>0)
{
cout<<(*rs).s<<" ";
n=n-(*rs).s;
rs=rs-(*rs).s;
}
}
voi