求解钢条切割问题的心路历程和思路

开门见山,我们直接上正题!
什么是钢条切割问题呢?是这样的:

        我们可以很直观地看到此处的题目要求:
1、随机生成钢条长度n和不同长度钢条的价格信息;
        这个说白了就是生成一下这个表嘛,我们直接找一个生成随机数的函数rand()就解决问题咯!
        用两个数组分别储存一下长度i和价格pi,轻松拿下的嘛。
2、确定一种钢条的切割方案,使公司的收益最大化。
        如果说上一条要求是准备工作的话,此处就是重头戏了,还是需要拿出笔纸好好考虑的。

要求只有两行,但是找到头绪还是挺重要的。
解决这个问题不能一蹴而就,但是我们可以先做好准备工作再慢慢考虑!

1、随机生成钢条长度n和不同长度钢条的价格信息

这里先放一段我写的代码,咱们对着代码简单说一下:

int i;
	printf("钢条最大长度是%d\n",length);

	for(i=1;i<length+1;i++)//随机生成价值范围
	//i<=v[i]<=3*i && v[i-1]<=v[i]
	//此处i表示钢条长度 v[i]表示对应长度的钢条价格

	{	
		v[i]=rand()%(3*i-v[i-1]+1)+v[i-1];//rand()%(b-a+1)+a,生成[0,b-a]上的随机数 
		printf("钢条长度%d,价值%d\n",i,v[i]);

	}


生成随机数嘛,我们直接运用到一个rand函数!
首先要加上: #include<stdlib.h> 才能用的捏。
为了找一下生成的随机数的范围,让我们简单的观察一下样例给出的这个表:
        第一行不用多说了,就是各个长度嘛。第二行的价格才是我们要用随机数生成的!
        于是我就自以为合理地设置了一个范围,也就是这段注释:
            //i<=v[i]<=3*i && v[i-1]<=v[i]
           
 //此处i表示钢条长度 v[i]表示对应长度的钢条价格

用语言表达就是说,每段长度的价格要符合两个条件:(此处仅用数值比较,不带单位)
        一是钢条的价格区间是在长度的值和三倍长度的值之间。
            比如钢条长度是2,那么它的价格就在[2,6]之间!当然还有一个条件是要取整数的。
        二是长的钢条不能比短的钢条价格低。
            比如长度2的钢条价格是4,那么长度为3的钢条价格就不能低于4了!
         设置这一点我感觉还是挺合理的,在这个题目下,切割是没有成本的。如果更长的钢条价格反而低了,那岂不是我买了便宜的长钢条,切割一下直接发大财了。所以有了这个限制。
         不过我并没有卡掉等于的情况,因为样表中存在着等于的数据。

确定了比较合理的范围,就差用代码实现它咯。此处附上一个rand()函数的使用方法链接:
(8条消息) C++中rand()函数的用法_风暴计划的博客-CSDN博客_c++ rand

ok,那我们就写一个看起来很冗长,实际上并不可怕的一句代码:

        v[i]=rand()%(3*i-v[i-1]+1)+v[i-1];//rand()%(b-a+1)+a,生成[0,b-a]上的随机数 

对了对了,此处的v[]长度是length+1,因为长度0是没有价格滴。
我们先让length=10,看看效果如何:

好像还行,就是跨度有点大……不过问题不大!
这样一来,准备工作就完成啦!

2、确定一种钢条的切割方案,使公司的收益最大化。

刚开始看到这个问题的时候,我是想贪心一下的,觉得求一下各个长度的单价直接去贪就能得到最优解。然而在我求单价的时候就受挫了……单价依然用到的是一个数组,用价格除以长度就可以了,不过这样我就面临着得到小数的问题。
当然最重要的是,我无法确定贪心得到的是最优解。因为单价类似于把钢条切成统一长度的不同几种,可能比较出来有大小,但是差距是否足够让我选择单价排名第一的而放弃后者,在随机数表的情况下这是没办法估计的。
于是抱着一堆问题,我只能上网看大家的解决方案了。答案是动态规划
(8条消息) 算法导论 — 15.1 钢条切割_yangtzhou的博客-CSDN博客_我们对钢条切割问题进行一点修改
上面不仅有解法,还提到了很多扩展,给了我很大的启发。
(这篇文章真的很厉害,非常非常可惜最近我的脑袋是一团糨糊,下来好好研究)
 

说到动态规划,我们可以想想这道题目的子问题是啥。
假设我们手里有一根长度为5的钢条,切一次会有什么样的结果呢?
显然就是1+4或者2+3啦。这两种情况中肯定有一种是最优解。
那么对于切割一次产生的两段钢条来说,必然也是有最优解的。除了它们本身的价格之外,4还可以分成1+3、2+2,3可以分成1+2,2可以分成1+1。如果是更长的钢条,自然也就会有更多的子问题。
每次切割就会产生两个新的解法完全一样的切割问题!这不就是子问题嘛!

那么这个子问题怎么解决呢?
假设我们手里有一根长度为2的钢条。想把它卖出去,有两种方案:一种直接卖,一种是切成两段长度为1的再卖出去。想得到最大收益,就是把这两种方案比一下,怎么卖得钱多就怎么卖呗。
那如果这个钢条长度变成了3呢?大概就是3、1+2、1+ 1+1了。再取其中最大值即可。
那答案就呼之欲出了:求所有方案中的最大值。

那么我们需要的核心代码就是一个递归函数!

const int length=10;
int r[length+1];//储存每个长度的钢条所能得到的最大价格。在递归函数中调用,设置成数组分别保存,如果只设置成一个变量会在每次调用时丢失 
int v[length+1];//储存钢条价格的数组 
int max(int a,int b){
	if(a>b)
	return a;
	else
	return b;
}
int qiege(int length){ //递归的切割函数 
	
	if(length==0)
		return 0;//钢条长度为0时,价格为0 
		
		int i;
		int result=v[length];//初始化时,将结果赋值为当前长度的钢条价格。i=0时不参与遍历 
 		for(i=1;i<length;i++){
 			result=max(result,v[i]+qiege(length-i));//核心代码,遍历所有情况 
 			count++;
 	}
 	r[length]=result;//将当前长度的钢条能获得的最大价格储存在数组里 
 	
 	return result;
	 
}

这里需要说的基本都给出了注释。
函数中i表示长度,没有将i=0放入循环中是因为v[0]的值是0。
每次递归的最优解需要一个数组储存,因为只使用一个变量的话每次递归都会更新,之前的量就都丢失了。
核心代码非常关键,这里使用了一个max函数实现取最大值。
        每一次递归返回的都是result这个已经取过最大值(最优解)的值,局部最优则整体最优。

到这里问题已经解决啦,我们看一下代码:

#include <stdio.h>

#include <stdlib.h>//使用了rand()函数 

const int length=10;
int r[length+1];//储存每个长度的钢条所能得到的最大价格。在递归函数中调用,设置成数组分别保存,如果只设置成一个变量会在每次调用时丢失 
int v[length+1];//储存钢条价格的数组 
int count=0;
int max(int a,int b){
	if(a>b)
	return a;
	else
	return b;
}
int qiege(int length){ //递归的切割函数 
	if(length==0)
		return 0;//钢条长度为0时,价格为0 
		
		int i;
		int result=v[length];//初始化时,将结果赋值为当前长度的钢条价格。i=0时不参与遍历 
 		for(i=1;i<length;i++){
 			result=max(result,v[i]+qiege(length-i));//核心代码,遍历所有情况 
 			count++;
 	}
 	r[length]=result;//将当前长度的钢条能获得的最大价格储存在数组里 
 	
 	return result;
	 
}
int main()

{

	int i;
	printf("钢条最大长度是%d\n",length);

	for(i=1;i<length+1;i++)//随机生成价值范围
	//i<=v[i]<=3*i && v[i-1]<=v[i]
	//此处i表示钢条长度 v[i]表示对应长度的钢条价格

	{	
		v[i]=rand()%(3*i-v[i-1]+1)+v[i-1];//rand()%(b-a+1)+a,生成[0,b-a]上的随机数 
		printf("钢条长度%d,价值%d\n",i,v[i]);

	}
	printf("最高收益是%d\n",qiege(length));
	printf("递归了%d次\n",count);
}

运行结果是这样的:

为啥多了一行“递归了511次”?因为这个代码可以用备忘录方法优化!
显然,此时重叠子问题被多次计算了。
假设我们有长度为4的钢条,在计算1+3这种情况时,我们还要计算3的解。有一种方案是1+2.那么还要计算2的解,有一种是1+1. 计算完毕后,4的下一种情况是2+2,我们还要计算一次2的最优解。这显然是浪费时间的。
那么何为备忘录呢?就是当我计算完一次2的最优解后将其储存起来,下一次再算2的最优解就直接从备忘录里拿就可以了,不需要再计算,以此类推,大大减少复杂度!
 

#include <stdio.h>

#include <stdlib.h>//使用了rand()函数 

const int length=10;
int r[length+1];//储存每个长度的钢条所能得到的最大价格。在递归函数中调用,设置成数组分别保存,如果只设置成一个变量会在每次调用时丢失 
int v[length+1];//储存钢条价格的数组 
int count=0;
int max(int a,int b){
	if(a>b)
	return a;
	else
	return b;
}
int qiege(int length){ //递归的切割函数 
	if(r[length]!=0)//优化代码,备忘录方法。r[length]不为0说明之前已经求过。数组r[]储存的已经是每种情况的最优解,直接调用即可 
		return r[length];//直接返回当前最优解 
	if(length==0)
		return 0;//钢条长度为0时,价格为0 
		
		int i;
		int result=v[length];//初始化时,将结果赋值为当前长度的钢条价格。i=0时不参与遍历 
 		for(i=1;i<length;i++){
 			result=max(result,v[i]+qiege(length-i));//核心代码,遍历所有情况 
 			count++;
 	}
 	r[length]=result;//将当前长度的钢条能获得的最大价格储存在数组里 
 	
 	return result;
	 
}
int main()

{

	int i;
	printf("钢条最大长度是%d\n",length);

	for(i=1;i<length+1;i++)//随机生成价值范围
	//i<=v[i]<=3*i && v[i-1]<=v[i]
	//此处i表示钢条长度 v[i]表示对应长度的钢条价格

	{	
		v[i]=rand()%(3*i-v[i-1]+1)+v[i-1];//rand()%(b-a+1)+a,生成[0,b-a]上的随机数 
		printf("钢条长度%d,价值%d\n",i,v[i]);

	}
	printf("最高收益是%d\n",qiege(length));
	printf("递归了%d次\n",count);
}

加入的代码是递归函数里的这两行:
            if(r[length]!=0)//优化代码,备忘录方法。r[length]不为0说明之前已经求过。数组r[]储存的已经是每种情况的最优解,直接调用即可 
                return r[length];//直接返回当前最优解 
运行结果是这样的:
可以看到,递归次数大大减少了!
 

这篇文章到这里就结束了,我曾尝试了将最优方案写进代码中,但是没有成功。这道题目还有一些方法和扩展,比如从底部开始向上推,这里没有放出。

感谢观看,请多给我评论和建议!

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值