【動態規劃】01揹包問題

01揹包問題

參考:
[https://blog.csdn.net/qq_38410730/article/details/81667885]
[https://www.cnblogs.com/arsenalfaninecnu/p/8945548.html])

什麼是01揹包問題?

01揹包問題描述:有編號分別爲a,b,c,d的N=4件物品,它們的重量w分別是2,3,4,5,它們的價值v分別是3,4,5,6,每件物品數量只有一個,現在給你個承重爲M=8的揹包,如何讓揹包裏裝入的物品具有最大的價值總和?總和是多少?

解題思路

結題思路是把該問題分解爲一個一個的小問題,一步步的通過小問題的最優解,最終得到大問題的最優解。在這個問題中有兩個維度的變量,一是揹包的容量M,二是物品的種類數N,假如M=1,只有一種物品 a,最優解是什麼,有a,b兩種物品是最優解是什麼?以此類推。

狀態轉移方程怎麼列?

1.首先,當物品種類N一定時,揹包容量越大,那麼最後的結果一定大於等於原來的值
2.其次,當揹包容量M一定時,物品種類N越多,最後的記過一定大於等於原來的值。
3.那麼,也就是說,我們想要求取的最大值也即是當M和N最大的時候的值。

假如當前有a和b兩種,M=1時,考慮a是否能夠放入,取得最大值,然後考慮a和b是否能放入,最終的情況可能只有a,或只有b或者ab都有。
對於b來說:

判斷b能否單獨放進揹包:
—如果不能,那麼備選爲a,b時最大值,等於備選只有a時的最大值(因爲b是放不進揹包的)。
—如果能,即b能夠放進去,還有兩種可能(即將b放進揹包,和不將b放進揹包),對這兩種可能性,要取最大值:
---------最終將b放進去(注:此時物品a是否被放進揹包是未知的,原因是:剩餘的揹包容量可能不足以放進物品a,即要在剩餘可選物品裏找出最優解。
---------最終沒有將b放進去(因爲後面可能有比b更合適的物品放進去),此時最大值等於備選只有a時的最大值
引自原文

簡單來說我們要思考的是第i個物品是否放進揹包
c++代碼如下:

if(j>=w[i]) 
    dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
    //dp[i][j]存儲i個物品,容量爲j時的最大值
else dp[i][j]=dp[i-1][j];

解題代碼如下:

#include<iostream>
using namespace std;
int m,n;//n是物品種類數,m是揹包容量 
int w[100],v[100]; 
long long dp[100][100];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
		else dp[i][j]=dp[i-1][j];
	}
	//上面的代碼可以計算出最值,輸出動態規劃表
	for(int i=1;i<=n;i++){
	    for(int j=1;j<=m;j++)
	        cout<<dp[i][j]<<" ";
	    cout<<endl;
	} 
   cout<<dp[n][m];
	
	return 0;
}
 

回溯最優解

求出最大值後我們還要找出最後那幾個物品被放進了揹包,用回溯的方法來尋找最後解,思路如下
首先,判斷dp[i][j]與dp[i-1][j-w[i]]+v[i]是否相等:
– 如果相等,則說明第i個物品沒有放進揹包,則繼續回溯(i-1,j);、
– 否則就是進了揹包,則回溯到(i-1,j-w[i])
定義函數如下:

void findMax(int i,int j){
	 
	if(i>=0){
		if(dp[i][j]==dp[i-1][j]){
			//如果滿足這個條件,說明第i個沒有放進揹包 
			item[i]=0;
			//那麼就回溯到i-1,j 
			findMax(i-1,j);
		}	
	    else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
	   {
	     	item[i]=1;
	        //如果放進揹包就回溯到下面 
	        findMax(i-1,j-w[i]);
     	}
	}

完整代碼:

#include<iostream>
using namespace std;
int m,n;//n是物品種類數,m是揹包容量 
int w[100],v[100];
int item[100];//標記是否被選中 
long long dp[100][100];
//回溯最優解
void findMax(int i,int j){
	 
	if(i>=0){
		if(dp[i][j]==dp[i-1][j]){
			//如果滿足這個條件,說明第i個沒有放進揹包 
			item[i]=0;
			//那麼就回溯到i-1,j 
			findMax(i-1,j);
		}
	    else if(j-w[i]>=0 && dp[i][j]==dp[i-1][j-w[i]]+v[i])
	   {
	     	item[i]=1;
	        //如果放進揹包就回溯到下面 
	        findMax(i-1,j-w[i]);
     	}
	}
	//一直遍歷直到i=0爲止 
} 
int main(){	
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
	
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		if(j>=w[i]) dp[i][j]=max(dp[i-1][j-w[i]]+v[i],dp[i-1][j]);
		else dp[i][j]=dp[i-1][j];
	}
	//上面的代碼可以計算出最值,輸出動態規劃表
	for(int i=1;i<=n;i++){
	    for(int j=1;j<=m;j++)
	        cout<<dp[i][j]<<" ";
	    cout<<endl;
	} 
	//尋找最優解並輸出 
	findMax(n,m); 
	for(int i=1;i<=n;i++) cout<<item[i]<<"  ";
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章