01背包问题---动态规划

这篇博客介绍了如何使用动态规划解决01背包问题,通过一个具体的例子展示了如何建立状态转移方程,并给出了相应的C++代码实现。博主详细解释了每个步骤,包括如何填充dp矩阵,以及如何通过回溯找出选择了哪些商品。最后,提供了完整代码供读者参考学习。

问题描述

🚩有N个物品,其重量分别为weight[i],其价值分别为value[i],现有一个容量为V的背包,问怎样装商品才能使得背包中的价值最大?

int N=5,V=10;
int weight[]={0,3,6,3,8,6};
int value[]={0,4,6,6,12,10};

思路讲解

可先看B站这个视频讲解:https://www.bilibili.com/video/BV1K4411X766/

其实就是相当于我们创建一个表格即dp[][],左边那一列表示商品编号,上面那一行表示背包容量,表格内容是此时状态的最大价值

先看结果,如下

在这里插入图片描述

解释:

一行一行的填写,从上至下从左至右。如编号1重量为3,那么容量为0、1、2时都不能放下,最大价值都为0

当背包重量为3时可以放下商品1所以最大价值为4

再考虑第二行,j等于0~2时都放不下最大价值为0,由于商品2重量为6,那么背包容量为3~5时都放不下商品2只能放商品1最大价值为商品1的价值4。

而当背包容量为6时,此时能够放下商品2,那么就要考虑要不要放尚品2

1️⃣放商品2,那么最大价值就是此时背包的容量减去商品2的重量为0,再看第一行重量为0时的价值也是0,那么最大价值为6

2️⃣不放商品2,那么最大价值就是第一行背包容量为6时的最大价值为4

放商品2最大价值为6,不放最大价值为4,我们选最大值为6

总结如下:

若背包容量小于商品n的重量放不下商品

  • 那么前n个商品的最大价值和前n-1个商品的最大价值是一样的(既然不放相当于就不考虑这个商品,那么考虑前n个商品其实就是考虑前n-1个商品)

若此时背包装得下商品n

  • 若选择放商品n,那么背包可用容量KV就等于背包总容量SV减去商品n的重量nV,那么问题就变成了只考虑前n-1个商品且背包容量为KV时的最大价值,而此前我们已经求得了这个值填写在了表中所以直接用这个值加上商品n的价值就是最大价值
  • 若不放商品n,那么前n个商品的最大价值和前n-1个商品的最大价值是一样的
  • 从以上两个选择选一个最大的就是最终的最大价值

对应的代码如下

for (int n = 1; n <= N; n++)	//遍历商品1~N
	{
		for (int v = 0; v <= V; v++)	//遍历容量0~V
		{
			if(v>=weight[n])	//背包容量大于等于商品n的容量时
			{
				dp[n][v]=max(dp[n-1][v-weight[n]]+value[n],dp[n-1][v]); //选择放商品n和不放商品n哪个价值最大
			}
			else	//背包容量小于商品n的容量时
			{
				dp[n][v]=dp[n-1][v];	//此时商品n一定不能放,此时最大价值就是商品1到i-1中,容量为v时的最大价值
			}
		}	
	}

而我们怎么知道求得的最大价值其中我们选了哪些商品呢

其实就是从表的右下角开始回溯,如果dp[n][j]和dp[n-1][j]值是一样的,那么我们肯定没装商品i(相当于我们就没考虑商品n)。事实上,我们推dp[n][j]时如果选择不放商品n那么我们就让dp[n][j]=dp[n-1][j]

所以相当于我们根据两个数值是否相等来判断当时我们是否选了商品n

相当于是求最大价值的一个逆过程

如果装了商品n,那么用总容量减去商品n的容量得到可用容量kV再来判断是否放了商品n-1,其实也就是看dp[n-1][kv]和dp[n-2][kv]是否相等,以此类推

int n=N,v=V;
while(n!=0)
{
	if(dp[n][v]==dp[n-1][v])
	{
		cout<<"商品"<<n<<"未被选中"<<endl;
	}			
	else
	{
		cout<<"商品"<<n<<"被选中"<<endl;
		v=v-weight[n];
	}
	n--;
}

🦄完整代码如下

#include<bits/stdc++.h>
using namespace std;
int dp[15][15];
int N=5,V=10;
int weight[]={0,3,6,3,8,6};
int value[]={0,4,6,6,12,10};

//打印初始状态
void printInit()
{
	cout<<"共"<<N<<"个商品,"<<"背包容量为"<<V<<endl;
	for (int i = 1; i <= N; i++)
	{
		cout<<"商品"<<i<<"的重量为"<<weight[i]<<",价值为"<<value[i]<<endl;
	}
	
}

//求最大价值
int maxValue()
{
	for (int n = 1; n <= N; n++)	//遍历商品1~N
	{
		for (int v = 0; v <= V; v++)	//遍历容量0~V
		{
			if(v>=weight[n])	//背包容量大于等于商品n的容量时
			{
				dp[n][v]=max(dp[n-1][v-weight[n]]+value[n],dp[n-1][v]); //选择放商品n和不放商品n哪个价值最大
			}
			else	//背包容量小于商品i的容量时
			{
				dp[n][v]=dp[n-1][v];	//此时商品n一定不能放,此时最大价值就是商品1到n-1中,容量为v时的最大价值
			}
		}	
	}
	return dp[N][V];
}

//打印最大价值的商品选择情况
void printSelect()
{
	int n=N,v=V;
	while(n!=0)
	{
		if(dp[n][v]==dp[n-1][v])
		{
			cout<<"商品"<<n<<"未被选中"<<endl;
		}			
		else
		{
			cout<<"商品"<<n<<"被选中"<<endl;
			v=v-weight[n];
		}
		n--;
	}
}

int main()
{	
	printInit();
	cout<<"最大价值为:"<<maxValue()<<endl;	
	printSelect();
	return 0;
}
### 01背包问题动态规划解法 #### 定义与背景 动态规划是一种通过将复杂问题分解为更简单子问题的方法来解决问题的技术[^1]。当面对像01背包这样的优化问题时,这种方法尤为有效。 #### 动态规划的核心概念 核心在于构建一个二维表`dp[i][j]`,其中`i`代表考虑的商品数量(即前`i`个商品),而`j`则表示可用的空间大小(即背包容量)。此表格用于存储在给定条件下可以获得的最大总价值[^5]。 #### 构建状态转移方程 对于每一个可能的状态`(i,j)`,存在两种情况: - 如果不选择第`i`项,则最大价值等于只考虑前`i-1`项且同样剩余空间`j`的情况下的最优解; - 若选择了第`i`项,则新的剩余空间变为`j-w_i`(前提是`w_i≤j`),此时最大价值应加上该项的价值`v_i`再加上仅限于前`i-1`项但在缩小后的空间内能取得的最佳结果。 最终的选择取决于哪种方案能够带来更高的收益。形式化表达如下: \[ dp[i][j]=\max(dp[i−1][j], v_i+dp[i−1][j−w_i]) \quad (if \; w_i ≤ j)\] 这里,\(w_i\) 和 \(v_i\) 分别指代第 `i` 物品的重量及其对应的价值[^4]。 #### 初始化条件 通常情况下,设置边界条件为:没有任何物品可选(`i=0`)或完全没有空间(`j=0`)的情况下,所能获取的最大价值自然都设定为零;另外还需注意初始化第一行数据——只有单件物品可供挑选时,在不超过其自身重量的前提下尽可能多地放入该物品以实现最大化价值。 #### Python代码示例 以下是基于上述理论编写的Python程序片段,用来计算并打印出针对特定输入参数集的结果: ```python def knapsack(weights, values, capacity): n = len(values) # 创建DP table 并全部设为0 dp = [[0 for _ in range(capacity + 1)] for __ in range(n + 1)] for i in range(1, n + 1): # 遍历每一件物品 for w in range(1, capacity + 1): # 对每个可能的背包容积进行迭代 if weights[i - 1] <= w: dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - weights[i - 1]] + values[i - 1]) else: dp[i][w] = dp[i - 1][w] return dp[n][capacity] weights = [20, 15, 10] values = [20, 30, 25] capacity = 25 print(f"The maximum value that can be put into the bag is {knapsack(weights, values, capacity)}") ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值