算法导论:动态规划 切钢条问题

文字参考算法导论第十五章

问题描述:不同长度的钢条,具有不同的价值,而切割工序没有成本支出,公司管理层希望知道最佳切割方案,假定钢条的长度均为整数:用数组v[I]表示钢条长度为I所具有的价值v[] = {0,1,5,8,9,10,17,17,20,24,30};用r[I]表示长度为I的钢条能获取的最大价值,通过观察可以知道,

r[1] = 1(无切割),

r[2] = 2(无切割),

r[3] = 3(无切割),

r[4] = 10(r[4] = r[2]+r[2],切割成两段长度为2的钢条),

r[5] = 13(r[5] = r[2]+r[3],切割成长度为2和3的钢条),

r[6] = 17(无切割),r[7] = 18(r[7] = r[1]+r[6]或r[2]+r[2]+r[3]),

r[8] =22(r[8] = r[6]+r[2])

,r[9] = 25(r[9] = r[3]+r[6])

,r[10]=30(无切割),

更一般的,我们可以用更简短的钢条的最优切割收益来描述它:

r[n] = max(v[n],r[1]+r[n-1],r[2]+r[n-2],…,r[n-1]+r[1]),

首先将钢条切割成长度为I和n-I的两段,接着求哪种方案获得最优,我们必须考察所有可能的I,选取其中收益最大者,如果不做任何切割收益最大,那么选取不切割的价值。注意到,为了求解规模为n的元问题,我们先求解形式完全一样,但规模更小的子问题。首次切割后,将两段钢条看成是两个独立问题的实例。通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的解,我们称其满足最优子结构,问题的最优子解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

第二种求解思路:我们将钢条从左边切下长度为I的一段,只对右边剩下的长度为n-I的一段继续切割(递归求解),即问题的分解方式为:将长度为n的钢条分解为左边开始一段,以及剩余部分继续分解的结果。

#include<iostream>
using namespace std;
int v[11] = {0,1,5,8,9,10,17,17,20,24,30};
int cut_rod(int n)//左端最优解为p,右端钢板剩余长度n,对右端继续切割 
{
	if(n==0)return 0;
	int q = -99999999;
	for(int i=1;i<=n;i++)
	q = max(q,v[i]+cut_rod(n-i));//cut_rod(n)返回长度为n的钢棒,所能获得的最大价值 
	return q;
}
int main()
{
	int p = 0;
	for(int k=1;k<=10;k++)
	cout<<cut_rod(k)<<endl;
}

该程序效率低下,原因是它反复地用相同的参数值对自身进行递归调用,即它反复求解相同的子问题。对于长度为n的钢条,它考察了所有2^(n-1)种可能的切割方案

改进:带备忘的自顶向下法,此方法仍然按递归的形式编写过程,但过程会保存每个子问题的解,当需要一个子问题时,首先检查子问题的解是否保存,如果是,直接返回保存的值,从而节省了计算时间

#include<iostream>
#include<string.h>
using namespace std;
int v[11] = {0,1,5,8,9,10,17,17,20,24,30},r[101];//问题规模最大为100 
int cut_rod(int n)//左端最优解为p,右端钢板剩余长度n,对右端继续切割 
{
	if(n==0)return 0;
	if(r[n]!=-1)return r[n];//如果保存了规模为n的解,直接返回 
	int q = -99999999;
	for(int i=1;i<=n;i++)
	q = max(q,v[i]+cut_rod(n-i));//cut_rod(n)返回长度为n的钢棒,所能获得的最大价值 
	r[n] = q;		//运行到这一步了,说明r[n]没有值,将求得的解存入r[n] 
	return q;
}
int main()
{
	int p = 0;
	//for(int k=1;k<=10;k++)
	memset(r,-1,sizeof(r));
	cout<<cut_rod(40)<<endl;
}

第二种方法为自底向上法,这种方法需要恰当定义子问题“规模”的概念,使得任何子问题的求解都能依靠更小的子问题求解,我们可以按问题的规模排序,按由小到大的顺序进行求解,当求解某个子问题时,它所依赖的那些更小的子问题都已经求解完毕,结果已经保存,每个子问题只求解一次,当我们求解它时,它的所有前提子问题都已经求解完成。

#include<iostream>
using namespace std;
const int inf = -9999999;
int v[] = {0,1,5,8,9,10,17,17,20,24,30};
int r[11];//用来记录每个子问题的最优解 
int cut_rod(int n)//对长度为n的钢条切割,能获得的最大价值 
{
	for(int i = 1;i<=n;i++)//从最小子问题开始 
	{
		int q = inf;
		for(int j = 1;j<=i;j++)
		{
			q = max(q,v[j]+r[i-j]);
		}
		r[i] = q;	//自底向上求解每个子问题,从长度为1的问题开始 ,并记录子问题的解 
	}
	return r[n];
}
int main()
{
	r[0] = 0;//长度为0的钢条能获取的最大价值为0,因为没有可取的钢条 

	for(int k=1;k<=10;k++)
	{
		cout<<"长度为:"<<k<<"最大值"<<endl;
		cout<<cut_rod(k)<<endl;
	}
		
	
}

《算法导论:动态规划 切钢条问题》

要求输出具体是怎么切的:只是多加一个数组

#include<iostream>
using namespace std;
const int inf = -9999999;
int v[] = {0,1,5,8,9,10,17,17,20,24,30};
int r[11];//用来记录每个子问题的最优解 
int s[11];
int cut_rod(int n)//对长度为n的钢条切割,能获得的最大价值 
{
	for(int i = 1;i<=n;i++)//从最小子问题开始 
	{
		int q = inf;
		for(int j = 1;j<=i;j++)
		{
			if(q<v[j]+r[i-j])
			{
				q = v[j]+r[i-j];
				s[i] = j; 	//在求解问题规模为i的子问题时将第一段的最优切割长度j保存在s[i]中 
			}
		}
		r[i] = q;	//自底向上求解每个子问题,从长度为1的问题开始 ,并记录子问题的解 
	}
	return r[n];
}
int main()
{
	r[0] = 0;//长度为0的钢条能获取的最大价值为0,因为没有可取的钢条 
	int n = 8;
	cout<<"长度为:"<<n<<endl<<"价值:"<<cut_rod(n)<<endl<<"切割为:";
	while(n>0)
	{
		cout<<s[n]<<" ";
		n-=s[n];
	}
	
}

    原文作者:动态规划
    原文地址: https://blog.csdn.net/chen_minghui/article/details/53040946
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞