资讯动态

动态规划解决01背包与完全背包问题详解

发布时间:2026/10/27 3:48:37 来源:尧图企业网站定制
1. 背包问题与动态规划基础背包问题是算法领域最经典的动态规划应用场景之一。01背包和完全背包作为背包问题的两种基本变体在实际工程和算法竞赛中有着广泛的应用。我们先从最基础的01背包问题开始解析。1.1 01背包问题定义01背包问题的典型描述是给定一个容量为W的背包和N个物品每个物品有重量w_i和价值v_i。要求选择物品装入背包使得背包中物品的总价值最大且总重量不超过背包容量。这里的01意味着每个物品要么完整放入1要么不放入0不能分割。举个例子假设背包容量W10有以下物品物品1重量2价值6物品2重量3价值5物品3重量6价值8物品4重量7价值9最优解是选择物品1和物品3总重量8总价值14。1.2 动态规划思路解析动态规划解决背包问题的核心在于状态定义和状态转移方程。对于01背包我们定义dp[i][j]表示考虑前i个物品在背包容量为j时能获得的最大价值。状态转移方程有两种情况不选第i个物品dp[i][j] dp[i-1][j]选第i个物品dp[i][j] dp[i-1][j-w_i] v_i 前提是j ≥ w_i我们取这两种情况的最大值作为dp[i][j]的值。1.3 二维DP实现示例def knapsack_01_2d(weights, values, W): n len(weights) dp [[0]*(W1) for _ in range(n1)] for i in range(1, n1): for j in range(1, W1): if weights[i-1] j: dp[i][j] max(dp[i-1][j], dp[i-1][j-weights[i-1]] values[i-1]) else: dp[i][j] dp[i-1][j] return dp[n][W]这个实现的时间复杂度是O(NW)空间复杂度也是O(NW)。虽然直观但空间效率不高特别是当W很大时。2. 一维DP优化技巧2.1 空间优化原理观察二维DP的实现可以发现dp[i][...]只依赖于dp[i-1][...]因此我们可以将二维数组压缩成一维数组通过逆序更新来避免覆盖还需要使用的数据。优化后的状态转移方程为 dp[j] max(dp[j], dp[j-w_i] v_i)需要从右向左更新dp数组确保在计算dp[j]时dp[j-w_i]还是上一轮的值。2.2 一维DP实现代码def knapsack_01_1d(weights, values, W): n len(weights) dp [0]*(W1) for i in range(n): for j in range(W, weights[i]-1, -1): dp[j] max(dp[j], dp[j-weights[i]] values[i]) return dp[W]这个实现将空间复杂度优化到了O(W)是工程实践中更常用的写法。关键点内层循环必须逆序否则会变成完全背包问题的解法。这是初学者最容易犯错的地方。2.3 边界条件处理在实际编码中有几个边界条件需要注意重量数组和价值数组长度要一致背包容量W必须是非负整数物品重量必须都是正整数否则问题性质会变化我们可以添加一些防御性代码def knapsack_01_1d_safe(weights, values, W): if len(weights) ! len(values) or W 0: return 0 if any(w 0 for w in weights): raise ValueError(All weights must be positive) dp [0]*(W1) for w, v in zip(weights, values): for j in range(W, w-1, -1): dp[j] max(dp[j], dp[j-w] v) return dp[W]3. 完全背包问题解析3.1 完全背包问题定义完全背包与01背包的区别在于每种物品可以选取无限次而不是只能选一次。其他条件相同。还是之前的例子但现在是完全背包背包容量W10物品1重量2价值6物品2重量3价值5物品3重量6价值8物品4重量7价值9最优解是选5个物品1总重量10总价值30。3.2 完全背包的状态转移完全背包的状态转移方程与01背包类似但选择物品i时不是从i-1转移而是可以从i转移因为可以重复选择dp[i][j] max(dp[i-1][j], dp[i][j-w_i] v_i)同样可以优化为一维DP但内层循环是正序的def knapsack_unbounded_1d(weights, values, W): dp [0]*(W1) for i in range(len(weights)): for j in range(weights[i], W1): dp[j] max(dp[j], dp[j-weights[i]] values[i]) return dp[W]3.3 完全背包的变种问题完全背包有一些常见的变种问题恰好装满背包初始化时dp[0]0其他dp[j]-∞求方案数dp[j] dp[j-w_i]求具体方案需要额外记录选择路径例如求恰好装满的方案数def knapsack_unbounded_count(weights, W): dp [0]*(W1) dp[0] 1 for w in weights: for j in range(w, W1): dp[j] dp[j-w] return dp[W]4. 实战应用与性能优化4.1 常见应用场景背包问题在实际中有很多应用资源分配问题如广告投放预算分配投资组合优化裁剪问题如布料裁剪密码学中的子集和问题4.2 性能优化技巧物品预处理去除重量超过W的物品对完全背包去除重量大价值小的物品对相同物品可以合并计数循环优化对于稀疏重量可以用字典代替数组使用位运算加速某些操作并行计算对于大规模问题可以并行计算不同重量区间4.3 常见错误与调试一维DP循环方向错误01背包必须逆序完全背包必须正序初始化问题普通背包问题初始化为0恰好装满问题需要特殊初始化索引越界确保j-w_i不越界Python中列表索引可以为负要特别注意调试时可以打印DP表格def debug_print(dp): for row in dp: print( .join(f{x:2} for x in row))5. 扩展与变种问题5.1 多维背包问题当限制条件不止一个时如重量和体积就变成了多维背包问题。状态定义需要增加维度def knapsack_2d(weights, volumes, values, W, V): dp [[0]*(V1) for _ in range(W1)] for w, v, val in zip(weights, volumes, values): for i in range(W, w-1, -1): for j in range(V, v-1, -1): dp[i][j] max(dp[i][j], dp[i-w][j-v] val) return dp[W][V]5.2 分组背包问题物品被分为若干组每组只能选一个物品。解法是先枚举组再枚举容量最后枚举组内物品def group_knapsack(groups, W): # groups: list of list of (weight, value) dp [0]*(W1) for group in groups: for j in range(W, -1, -1): for w, v in group: if j w: dp[j] max(dp[j], dp[j-w] v) return dp[W]5.3 背包问题的其他变种有依赖的背包某些物品必须在其他物品被选时才能选树形背包物品组织成树形结构选子节点必须选父节点背包求第k优解维护前k优的解而非仅最优解这些变种问题在算法竞赛中经常出现需要根据具体情况调整状态定义和转移方程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑