钢条切割问题

(来自算法导论P204)

问题描述:

给定一段长度为n英寸的钢条和一个价格表Pi(i=1,2,3,...,n),求切割钢条方案,使收益rn最大。

思路(递归):

在钢条左边切割i段,再对右边剩余长度n-i段继续进行切割(递归求解),对左边的一段的则不再进行切割。

所以有:若不做切割第一段长度为n,收益为pn,剩余部分长度为0,对应的收益ro=0;

则求解公式:r(n)=max(1<=i<=n)(p(i)+r(n-i)).

不难由组合数学知道共有2次方n-1种情况。(n个钢条有n-1个可能的切割点,每个切割点有两种情况(切,不切)),不难知道如果不考虑位置关系,有大量重复。


时间复杂度求解:T(N)=1+T(0)+T(1)+...+T(N-1);

可求得到:T(N)-T(N-1)=T(N-1);

T(N)=2^n;


用动态规划解决:由于递归求解了大量子问题,用时空权衡

方法一:自顶向下带备忘的:(思维难度较小)

c++代码实现:

//自顶向下
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
int cut_ans(int r[],int p[],int n);
int main()
{
    int p[100]={0,1,5,8,9,10,17,17,20,24,30},n;
    int r[100];                           // 时空权衡。
    memset(r,-1,sizeof(r));
  //  for(int i=0;i<100;i++)
   //     p[i]=i;
    cin>>n;
    cout<<cut_ans(r,p,n)<<endl;
    return 0;
}

int cut_ans(int r[],int p[],int n)
{
    int q=-1;
    if(r[n]>=0) return r[n];
    if(n==0) q=0;
    else for(int i=1;i<=n;i++)
            q=max(q,cut_ans(r,p,n-i)+p[i]);    //q 取循环过程结果的最大值,我记得我原来用了条件句来记录最高点,实在愚昧。。
    r[n]=q;
    return r[n];
}

// 如果想知道时间效率提高了多么高,可以把我中间“//”的去掉,然后测n<100时有多快、。


方法2:自底向上:

和自顶向上具有相同的复杂度,由于没有频繁的递归函数调用开销,将比自顶向下的时间复杂性函数具有更小的系数。

c++代码实现:

//自顶向上
#include<iostream>
#include<algorithm>
using namespace std;
int cut_ans(int r[],int p[],int n );
int main()
{
    int r[100];
    r[0]=0;
    int p[100]={0,1,5,8,9,10,17,17,20,24,30},n;
    cin>>n;
    cout<<cut_ans(r,p,n)<<endl;
    return 0;
}
int cut_ans(int r[],int p[],int n)
{

    for(int m=1;m<=n;m++)
    {
        int q=-1;
        for(int i=0;i<m;i++)
        {
            q=max(q,r[i]+p[m-i]);
            r[m]=q;

        }

    }
    return r[n];

}

重构解

如何输出对应的具体切割方法?

这时候就要动点脑筋,每次求解出n长度对应的最优解r(n)时候记录下左边第一段切割的长度。记录在s【n】数组里面。

因为用了dp,所以会依次记录下从1 to n的左边第一段的切割的长度。

输出先上伪代码再分析:

print——cut-rod-solution(p,n)

(r,s)=extended-bottom-up-cut-rod(p,n)

while n>0

print s[n];

n=n-s[n];

这样的循环输出第一段长度就自然输出了对于n的具体切割的每段长度的解。


再上重构解的代码:

重构解: 
extaned -bottom-up-cut-rod(p,n)
	let r[0...n] and s[0...n] be new arrays
	r[0]=0;
	for j=1 to n
		q=-无穷
		for i=1 to j
			if q<p[i]+r[j-i]
				q=p[i]+r[j-i]
				s[j]=i;
				r[j]=q
	return r and s 



还有一个问题的延伸:每次切割还需要固定的成本,设计一个动态规划求解问题。

解答:(仍然是自顶向下和自底向上)

//切割成本为c时的自顶而下的dp 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
	int cut_ans(int r[],int p[],int n ,int c); 
	int r[100],c,n;            
	memset(r,-1,sizeof(r));        
	r[0]=0;
	int p[100]={0,1,5,8,9,10,17,17,20,24,30};
	cin>>c>>n;
	cout<<cut_ans(r,p,n,c)<<endl;
}
int cut_ans(int r[],int p[],int n,int c )
{
	int q=-1;
	if(n==0) return r[0];
	if(r[n]>0) return r[n];
	for(int i=1;i<=n;i++)
	{
		q=max(q,p[i]+cut_ans(r,p,n-i,c)-c*(i!=n));		//当i!=c时,也就是在中间某处切割了一次,所以收益减去c。当i==c时,也就是不切割,收益不用减去c。 
	}
	r[n]=q;
	return r[n];
}
*/
	
//切割成本为c时的自底而上的dp 	
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int main()
{
	int cut_ans(int r[],int p[],int n ,int c); 
	int r[100]={0},c,n;                  
	int p[100]={0,1,5,8,9,10,17,17,20,24,30};
	cin>>c>>n;
	cout<<cut_ans(r,p,n,c)<<endl;
}
int cut_ans(int r[],int p[],int n,int c )
{
	for(int i=1;i<=n;i++)
	{
		int q=-1;
		for(int j=1;j<=i;j++)
		{
			q=max(q,p[j]+r[i-j]-c*(j==i));     当i!=c时,也就是在中间某处切割了一次,所以收益减去c。当i==c时,也就是不切割,收益不用减去c。  
		}
		r[i]=q;
	}
	return r[n];
		
}

可以用输入(1,5);(2,5);(100,5)验证。

应该输出12,11,10。



思路总结(算法导论)

1.刻画一个最优解的结构特征。

2.递归的定义最优解的值。

3.计算最优解的值,通常采用自底向上的方法。

4.利用计算信息构造一个最优解。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值