动态规划(一)

#动态规划(一)
算法导论动态规划——钢条切割

  • **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}2n1(长度为n的钢条,可切割点有n-1个,可以选择切或不切)

  • 假设最佳方案为将钢条切割为k段,分别为i1{i_1}i1i2{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}}rn1,···ri{r_i}ri+rn−i{r_{n-i}}rni)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}}rni),其中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];
	}


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值