动态规划(一)

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

  • **Serling公司购买长钢条,将其切割为短钢条出售。切割工序本身没有成本支出。公司管理层希望知道最佳的切割方案。假定我们知道Serling公司出售一段长为i英寸的钢条的价格为pi(i=1,2,…,单位为美元)。钢条的长度均为整英寸。图15-1给出了一个价格表的样例。
    这里写图片描述
    钢条切割问题是这样的:给定一段长度为n英寸的钢条和一个价格表 p i {p_i} pi(i=1,2,…n),求切割钢条方案,使得销售收益 r n {r_n} rn最大。注意,如果长度为n英寸的钢条的价格 p n {p_n} pn足够大,最优解可能就是完全不需要切割。

问题分析

分割钢条的所有方案共有 2 n − 1 2^{n-1} 2n1(长度为n的钢条,可切割点有n-1个,可以选择切或不切)

  • 假设最佳方案为将钢条切割为k段,分别为 i 1 {i_1} i1 i 2 {i_2} i2,··· i k {i_k} ik;对于各段得到的最大收益分别为人 r 1 {r_1} r1, r 2 {r_2} r2,··· r k {r_k} rk;
  • 计算ri是小于规模n的新的子问题,且 r 1 {r_1} r1, r 2 {r_2} r2,··· r k {r_k} rk,可能有相互重叠部分。
  • 这个题可以用深搜做,遍历所有方案做比较,时间效率为指数级。
  • 若用动态规划做:分析样例表:
  • n=1时, r 1 {r_1} r1=1;
  • n=2时, r 2 {r_2} r2=5;
  • n=3时, r 3 {r_3} r3=8;
  • n=4时, r 4 {r_4} r4= r 2 {r_2} r2+ r 2 {r_2} r2=10;
  • n=5时, r 5 {r_5} r5= r 2 {r_2} r2+ r 3 {r_3} r3=13;
  • n=6时, r 6 {r_6} r6=17;
  • n=7时, r 7 {r_7} r7= r 2 {r_2} r2+ r 5 {r_5} r5= r 2 {r_2} r2+ r 2 {r_2} r2+ r 3 {r_3} r3=18
  • n=8时, r 8 {r_8} r8= r 3 {r_3} r3+ r 5 {r_5} r5=21
  • n=9时, r 9 {r_9} r9= r 3 {r_3} r3+ r 6 {r_6} r6= r 4 {r_4} r4+ r 5 {r_5} r5=25
  • n=10时, r 10 {r_{10}} r10=30;
    方法一:选择第一个切割点:
    r n {r_n} rn=max( p n {p_n} pn, r 1 {r_1} r1+ r n − 1 {r_{n-1}} rn1,··· r i {r_i} ri+ r n − 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的一段继续进行切割,对左边的一段则不再进行切割。这样得到的公式为:。这样原问题的最优解只包含一个相关子问题(右端剩余部分)的解,而不是两个。:
这里写图片描述
r n {r_n} rn=max( p i {p_i} pi+ r n − i {r_{n-i}} rni),其中i>=n/2&&i<=n;
当i=n时, r n {r_n} rn=( p n {p_n} pn+ r 0 {r_{0}} r0)= p n {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];
	}


}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值