1. 程式人生 > >演算法導論:動態規劃 切鋼條問題

演算法導論:動態規劃 切鋼條問題

文字參考演算法導論第十五章

問題描述:不同長度的鋼條,具有不同的價值,而切割工序沒有成本支出,公司管理層希望知道最佳切割方案,假定鋼條的長度均為整數:用陣列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];
	}
	
}