概括总结一下01背包问题的动态规划解决方法。
01背包问题概述:
给定 $N$ 件物品和一个容量为 $V$ 的背包。每件物品 $i$ 有一个体积(或重量)$v_i$ 和一个价值 $w_i$。目标是选择一部分物品放入背包,使得物品总体积不超过背包容量 $V$,且总价值最大。每件物品只能选择一次(要么选,要么不选)。
核心解决思路:动态规划 (Dynamic Programming, DP)
动态规划的核心思想是将问题分解为重叠的子问题,并通过存储子问题的解来避免重复计算。对于01背包问题,关键在于对每个物品做决策:选还是不选。
以二叉树来理解重叠子问题的意思,一颗整树我把它分成左右两个部分,然后再把这”左”部分再次分为左右两个部分,把“右”部分也分成左右两个部分。
在宏观上使用的解决问题的方法,再微观上也使用同样的方法来解决。
方法一:二维动态规划(标准解法)
-
状态定义:
$dp[i][j]$ 表示:从前 $i$ 件物品中任意选择,放入容量为 $j$ 的背包中所能获得的最大总价值。- $i$: 代表考虑到了第 $i$ 件物品(通常物品编号从 0 到 $N$)。
- $j$: 代表当前的背包容量(从 0 到 $V$)。
-
状态转移方程:
对于第 $i$ 件物品(体积 $v_i$,价值 $w_i$)和当前容量 $j$:- 不选择第 $i$ 件物品: 最大价值与只考虑前 $i-1$ 件物品在容量 $j$ 下的价值相同,即 $dp[i-1][j]$。
- 选择第 $i$ 件物品(前提是 $j >= v_i$,即当前容量能装下该物品): 价值为第 $i$ 件物品的价值 $w_i$ 加上用前 $i-1$ 件物品填充剩余容量 $j - v_i$ 所能获得的最大价值,即 $dp[i-1][j - v_i] + w_i$。
综合起来,$dp[i][j]$ 取两者中的较大值:
如果 $j < v_i$ (装不下第 $i$ 件物品):$dp[i][j] = dp[i-1][j]$
如果 $j >= v_i$ (可以装下第 $i$ 件物品):$dp[i][j] = max(dp[i-1][j], dp[i-1][j - v[i]] + w_i)$ -
初始化/边界条件:
- $dp[0][j] = 0$ 对于所有 $j$ (0 <= $j$ <= $V$):不考虑任何物品时,价值为0。
- $dp[i][0] = 0$ 对于所有 $i$ (0 <= $i$ <= $N$):背包容量为0时,价值为0。
通常将整个 $dp$ 数组初始化为0即可满足这些条件。
-
计算顺序:
- 外层循环遍历物品 $i$ (从1到 $N$)。
- 内层循环遍历背包容量 $j$ (从0或1到 $V$)。
-
最终答案:
$dp[N][V]$ 即为所求的最大价值。 -
复杂度:
- 时间复杂度:$O(N*V)$,因为需要填充 $N*V$ 个状态,每个状态计算是常数时间。
- 空间复杂度:$O(N*V)$,因为需要存储整个二维 $dp$ 表。
方法二:一维动态规划(空间优化解法)
观察到 $dp[i][j]$ 的计算只依赖于 $dp[i-1]$ 行的数据,因此可以优化空间。
-
状态定义:
$dp[j]$ 表示:容量为 $j$ 的背包所能获得的最大总价值。(这里隐含了“已经考虑了当前以及之前所有物品”这一层含义) -
状态转移方程:
当我们考虑一件新的物品 $i$(体积 $v_i$,价值 $w_i$)时,更新 $dp[j]$:
$dp[j] = max(dp[j], dp[j - v_i] + w_i)$- $dp[j]$ (在 $max$ 的第一个参数中):代表不选择当前物品 $i$ 时,容量 $j$ 的最大价值(这个值是上一轮迭代留下来的,即只考虑前 $i-1$ 件物品时的 $dp[j]$)。
- $dp[j - v_i] + w_i$:代表选择当前物品 $i$ 时的价值。
-
初始化/边界条件:
$dp$ 数组所有元素初始化为0。$dp[0]$ 始终为0。 -
计算顺序(关键点):
- 外层循环遍历物品 $i$ (从1到 $N$,或者物品数组从0到 $N-1$)。
- 内层循环必须逆序遍历背包容量 $j$ (从 $V$ 向下到 $v_i$)。
- 原因: 逆序遍历是为了保证在计算 $dp[j]$ 时,所用到的 $dp[j - v_i]$ 是上一轮迭代(即处理前 $i-1$ 件物品时)的结果。如果正序遍历,$dp[j - v_i]$ 可能已经是本轮考虑物品 $i$ 时更新过的值,这会导致物品 $i$ 被重复计算,问题就变成了完全背包问题。
-
最终答案:
$dp[V]$ 即为所求的最大价值。 -
复杂度:
- 时间复杂度:$O(N*V)$,两层循环。
- 空间复杂度:$O(V)$,只需要一个一维数组。
总结:
01背包问题是动态规划的经典入门问题。其核心在于对每个物品的“选”与“不选”进行决策,并构建状态转移方程。
二维DP思路直接,易于理解,但空间开销较大。
一维DP是对二维DP的空间优化,通过巧妙地改变内层循环(容量)的遍历顺序(改为逆序),使得可以用一维数组完成状态转移,大大降低了空间复杂度,是实际应用中更常用的方法。
两种方法的时间复杂度相同,主要区别在于空间复杂度。在物品数量或背包容量非常大时,空间优化显得尤为重要。
再总结
动态规划是一个复杂的递推而已。以前学过的数列的通项公式是一样的,数列的通项公式中 $a_n$ 由 $a_{n - 1}$构成的一个表达式,这样数列的当前项 $a_n$ 就可以由前面的项$a_{n-1}$计算出来。
只不过动态规划是在一个平面上做的递推,就好像查表计算一样。
难点就是构建这个平面上的通项公式而已,慢慢体会。
所谓的递推字面意思,由下往上叫做递,由前(已知)到后(未知)叫做推,感谢当初的命名者,您精准的的提取了这种算法的精髓,形象的用汉语描述了它,虽然我不知道您的名字,我仍然要感谢您或您们。
太强了