【动态规划】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;
}

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