贪心算法与动态规划:从分数背包到0-1背包的算法抉择
2026/8/3 20:46:53 网站建设 项目流程

1. 背包问题的现实困境与算法抉择

在资源有限的世界里,如何做出最优的分配决策,几乎是每个人每天都要面对的难题。无论是物流公司的货车装载、投资经理的资金配置,还是你周末去超市采购,手里拿着一笔预算,面对琳琅满目的商品,如何挑选才能让总价值最大化?这背后,其实都隐藏着一个经典的计算机科学和运筹学模型——背包问题。

背包问题之所以经典,是因为它用一个极其简单的场景,抽象出了资源分配的普适性困境:一个容量有限的背包,一堆重量和价值各不相同的物品,目标是在不超过背包容量的前提下,使得装入背包的物品总价值最高。听起来很简单,对吧?但魔鬼藏在细节里。当物品不能被分割,必须整个拿走或者整个留下时,问题就变成了“0-1背包问题”;而当物品是像金砂、石油这类可以按任意比例分割时,问题就变成了“分数背包问题”。这两种看似微小的差异,却导致了完全不同的解决思路和计算复杂度。

今天,我们不谈枯燥的理论,就从这两个最基础的背包问题变体入手,深入探讨贪心算法在其中扮演的角色。你会发现,贪心算法在分数背包问题中是一个“完美”的解题高手,但在0-1背包问题上,它却可能带你走入歧途。理解这背后的“为什么”,不仅能帮你写出更高效的代码,更能让你在面对现实决策时,拥有更清晰的算法思维。无论你是正在准备算法面试的学生,还是需要优化业务逻辑的开发者,这篇文章都将带你从原理到实现,彻底搞懂这两种背包问题,并掌握贪心算法的正确使用姿势。

2. 贪心算法的核心思想:局部最优与全局最优的博弈

在深入背包问题之前,我们必须先理解今天的主角——贪心算法。很多人对贪心算法有个误解,认为它就是一种“短视”的、每次都选当前最好选项的方法。这种说法只对了一半,更准确地说,贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法策略。

它的核心运作模式就像它的名字一样“贪婪”:在每一个决策点,它只盯着眼前利益最大的那个选项,毫不犹豫地拿下,然后基于新的状态继续寻找下一个最大利益点,如此反复,直到问题解决。它从不回头,也不考虑未来的可能性,这种“活在当下”的特性,既是它效率高的原因,也是它可能得不到最优解的风险所在。

贪心算法能成功应用,关键在于问题是否具备两个重要性质:

  1. 贪心选择性质:一个问题的整体最优解可以通过一系列局部最优(贪心)选择来达到。也就是说,我们不需要考虑所有可能的解,只需要每一步都选最好的,最后拼起来就是最好的。
  2. 最优子结构性质:一个问题的最优解包含了其子问题的最优解。解决了子问题,组合起来就能得到原问题的最优解。

为了让你更直观地理解,我们可以举一个生活中的例子:假设你要从一堆零钱中凑出100元,目标是使用的硬币数量最少。在人民币硬币体系(1元、5角、1角)中,贪心策略是有效的:每次都先拿最大面值且不超过剩余金额的硬币。要凑98元?先拿50元(假设有50元纸币),再拿20元,再拿20元,最后拿5元、2元、1元。这个过程每一步都是当前最优选择,最终也得到了全局最优解(硬币数最少)。

但是,如果硬币体系变了,比如有1元、7角和5角三种硬币,要凑出1元4角。贪心策略会先拿1元,剩余4角,然后只能拿两个5角?不对,4角小于5角,所以只能拿四个1角(假设有1角)。最终用了5个硬币(1个1元,4个1角)。然而,最优解其实是两个7角硬币,只用2个硬币。看,贪心算法在这里就失效了,因为它第一步的“最优选择”(拿1元)实际上堵死了后面得到更优解的道路。

注意:贪心算法的高效性(通常是线性或对数复杂度)和简洁性使其非常诱人,但在应用前,必须严格验证问题是否满足贪心选择性质。很多动态规划问题(如0-1背包)就是因为不满足贪心选择性质,才需要更复杂的解法。

所以,当我们面对背包问题时,第一个要问自己的就是:这个问题满足贪心选择性质吗?分数背包和0-1背包会给出截然不同的答案,这也决定了我们工具箱里该拿出哪件武器。

3. 分数背包问题:贪心算法的标准舞台

分数背包问题,有时也叫部分背包问题,是贪心算法教科书般的应用案例。它的规则对“贪婪”非常友好:有一批物品,每种物品有重量(w_i)和价值(v_i),你可以拿走物品的任意一部分(比如0.3个,0.5个)。背包有一个总容量限制(W)。目标同样是最大化总价值。

为什么贪心算法在这里能大显身手?关键在于“可分割”。既然物品可以按需切分,那么我们就不必纠结于“拿不拿整个”的二元选择。我们可以转换思路,不再比较物品的“绝对价值”,而是比较它们的“单位价值”或“价值密度”(即 v_i / w_i)。直觉告诉我们,单位价值最高的物品,显然“性价比”最高,应该优先拿。

这个直觉正是贪心选择性质在此问题上的体现:全局最优解中,一定包含了单位价值最高物品的尽可能多的部分。我们可以用反证法简单理解:假设全局最优解中没有拿单位价值最高的物品A,而是拿了部分单位价值较低的物品B。那么,我完全可以从B中拿出一部分重量,换成同等重量的A,因为A的单位价值更高,所以替换后总价值会增加,这与“最优解”矛盾。因此,最优解必须优先装单位价值最高的物品。

3.1 算法步骤与详细实现

基于以上分析,分数背包的贪心算法步骤清晰明了:

  1. 计算价值密度:遍历所有物品,计算每个物品的单位价值(价值/重量)。
  2. 降序排序:将所有物品按照单位价值从高到低进行排序。
  3. 贪心装载:按排序后的顺序,依次尝试将物品装入背包。
    • 如果当前物品的重量 ≤ 背包剩余容量,则将其全部装入,更新背包剩余容量和总价值。
    • 如果当前物品的重量 > 背包剩余容量,则只装入背包剩余容量所能容纳的部分(分数),计算这部分的价值(单位价值 * 剩余容量),装入后背包容量变为0,算法结束。
  4. 返回结果:当背包被完全装满或所有物品都被考虑过后,算法结束,返回获得的总价值。

下面我们用Python来实现这个算法,并附上详细的注释:

class Item: """物品类,封装重量、价值和计算出的单位价值""" def __init__(self, weight, value): self.weight = weight self.value = value # 计算价值密度,避免除零错误 self.ratio = value / weight if weight > 0 else 0 def __repr__(self): # 方便打印调试 return f"Item(w={self.weight}, v={self.value}, ratio={self.ratio:.2f})" def fractional_knapsack_greedy(items, capacity): """ 使用贪心算法解决分数背包问题 :param items: Item对象的列表 :param capacity: 背包总容量 :return: 能够获得的最大总价值 """ # 第一步:按单位价值(价值密度)降序排序 # 这是贪心策略的核心,排序复杂度为 O(n log n),是算法的主要开销 sorted_items = sorted(items, key=lambda x: x.ratio, reverse=True) total_value = 0.0 # 使用浮点数以容纳分数价值 remaining_capacity = capacity # 第二步:遍历排序后的物品列表 for item in sorted_items: if remaining_capacity <= 0: # 背包已满,无需继续 break if item.weight <= remaining_capacity: # 情况1:当前物品可以全部装入 total_value += item.value remaining_capacity -= item.weight print(f"全部装入 {item}, 更新总价值: {total_value:.2f}, 剩余容量: {remaining_capacity}") else: # 情况2:只能装入一部分(分数) # 计算能装入的比例所对应的价值 fraction = remaining_capacity / item.weight value_taken = item.value * fraction total_value += value_taken print(f"部分装入 {item}, 比例: {fraction:.2f}, 获得价值: {value_taken:.2f}") remaining_capacity = 0 # 背包在此后已满 break # 背包已满,循环结束 return total_value # 示例运行 if __name__ == "__main__": # 定义物品: (重量, 价值) item_data = [(10, 60), (20, 100), (30, 120)] items = [Item(w, v) for w, v in item_data] knapsack_capacity = 50 print("物品列表:", items) print(f"背包容量:{knapsack_capacity}") print("\n--- 贪心装载过程 ---") max_value = fractional_knapsack_greedy(items, knapsack_capacity) print(f"\n最终获得的最大总价值为:{max_value:.2f}")

运行上述代码,你会看到如下过程:

物品列表: [Item(w=10, v=60, ratio=6.00), Item(w=20, v=100, ratio=5.00), Item(w=30, v=120, ratio=4.00)] 背包容量:50 --- 贪心装载过程 --- 全部装入 Item(w=10, v=60, ratio=6.00), 更新总价值: 60.00, 剩余容量: 40 全部装入 Item(w=20, v=100, ratio=5.00), 更新总价值: 160.00, 剩余容量: 20 部分装入 Item(w=30, v=120, ratio=4.00), 比例: 0.67, 获得价值: 80.00 最终获得的最大总价值为:240.00

算法先拿走了单位价值最高的物品1(全部),然后拿走了物品2(全部),最后背包还剩20容量,而单位价值最低的物品3重量为30,所以只取其20/30 ≈ 0.67部分,获得120 * 0.67 = 80的价值。总价值60+100+80=240。

3.2 算法正确性证明与复杂度分析

为什么这个贪心策略对分数背包问题是最优的?我们可以用一种“替换论证”的思路来理解。假设存在一个最优解O,它的装载顺序不是按单位价值降序的。那么在这个解中,一定能找到两个物品i和j,i在j之前被(部分)装载,但物品i的单位价值低于物品j。由于物品可以分割,我们可以从物品i的装载份额中拿出一小部分重量δ,用来多装载一点物品j。因为物品j的单位价值更高,所以这一替换操作会使得总价值增加(δ * (ratio_j - ratio_i) > 0)。这意味着原来的解O并不是最优的,矛盾。因此,任何最优解都必须等价于按单位价值降序装载得到的解。

时间复杂度:算法的主要开销在于对n个物品按单位价值排序,时间复杂度为O(n log n)。之后的贪心装载过程是线性扫描,复杂度为O(n)。因此,总时间复杂度为O(n log n)。这是一个非常高效的算法。

空间复杂度:除了存储物品列表外,我们只需要常数级别的额外空间(用于记录总价值和剩余容量),因此空间复杂度为O(1),如果考虑存储物品列表本身,则为O(n)。

实操心得:在实现时,务必注意处理除零错误(物品重量为0的情况,理论上其单位价值为无穷大,应优先处理)。另外,对于浮点数计算,在比较剩余容量或输出最终结果时,可能会遇到精度问题,在要求严格的场景下,可以考虑使用分数(fractions.Fraction)或整数运算(将所有重量和价值乘以一个公倍数)来避免。

4. 0-1背包问题:贪心算法的滑铁卢

现在,让我们把规则改一下,这就是经典的0-1背包问题:物品还是那些物品,重量和价值属性不变,但这次,每个物品要么整个被放入背包(选择1),要么完全不放入(选择0),不能被分割。目标同样是总价值最大化,且总重量不超过背包容量W。

如果你试图把分数背包的贪心策略直接套用过来,即按单位价值降序排序,然后依次尝试装入整个物品,装不下就跳过,会发生什么?让我们通过一个经典的陷阱例子来看。

假设背包容量W=50,有三个物品:

  • 物品A:重量10,价值60,单位价值6.0
  • 物品B:重量20,价值100,单位价值5.0
  • 物品C:重量30,价值120,单位价值4.0

贪心策略(按单位价值)会先拿A(重10,值60),剩余容量40。再拿B(重20,值100),剩余容量20。最后看C(重30),装不下了。于是贪心解的总价值是60+100=160。

然而,最优解是什么呢?如果我们不拿A和B,而是只拿一个C,价值是120,显然不如160。但如果我们拿B和C呢?重量20+30=50,刚好装满,总价值100+120=220。这个解明显优于贪心解得到的160!贪心算法因为过早地拿走了重量轻、单位价值高的A,占用了10的容量,导致无法容纳B和C这个更优的组合。

这个例子清晰地揭示了0-1背包问题不满足贪心选择性质。当前单位价值最高的物品,并不一定出现在全局最优解中。因为物品的不可分割性,选择了一个物品,可能会“挤占”掉一个或多个其他物品组合的“位置”,而这个组合的总价值可能更高。这就破坏了贪心算法“每一步局部最优能导致全局最优”的基础。

4.1 为何贪心策略在此失效?深入剖析

贪心策略在0-1背包上的失败,根源在于问题的“离散性”和“组合爆炸”。在分数背包中,决策空间是连续的,我们可以用“价值密度”这个单一维度来线性排序和切割,最优解的结构很清晰。但在0-1背包中,决策是二元的,我们需要在2^n种可能的物品组合(n为物品数量)中寻找最优解。单位价值高但重量也大的物品,和单位价值稍低但重量很轻的物品,如何搭配才能填满背包并最大化价值,这是一个复杂的组合优化问题。

另一种常见的错误贪心策略是“按价值排序”或“按重量排序”。按价值排序会倾向于先拿价值高的重物,可能很快耗尽容量,错过多个轻量高价值物品的组合。按重量排序(拿最轻的)则可能塞满了一堆低价值的小物件,浪费了容纳高价值大件的机会。这些简单的单一维度贪心策略,都无法保证在0-1约束下找到最优解。

那么,0-1背包问题该如何解决?这就引出了计算机算法中另一个强大的范式——动态规划。

5. 0-1背包问题的动态规划解法

既然贪心算法行不通,我们就需要一种能够考虑所有可能组合,并避免重复计算的方法。动态规划通过将大问题分解为重叠子问题,并存储子问题的解(记忆化),从而高效地解决这类具有最优子结构性质的问题。0-1背包问题正具备最优子结构:考虑前i个物品、容量为j的背包的最优解,它必然和考虑前i-1个物品、容量为j或j-w_i的背包的最优解有关。

5.1 动态规划的状态定义与递推关系

我们定义一个二维数组dp[i][j],表示考虑前i个物品(物品编号从1到i),在背包容量恰好为j的情况下,能够获得的最大价值。这里“考虑”意味着我们可以选择拿或者不拿第i个物品。

对于每个dp[i][j],我们面临两种选择:

  1. 不拿第i个物品:那么问题就退化成了“考虑前i-1个物品,容量为j”的子问题,最优价值就是dp[i-1][j]
  2. 拿第i个物品:前提是背包容量j必须大于等于物品i的重量w[i]。如果拿了,我们需要消耗w[i]的容量,并获得v[i]的价值,剩余容量为j - w[i]用来装前i-1个物品。因此,这种情况下的最优价值是v[i] + dp[i-1][j - w[i]]

我们的目标是最大化总价值,所以dp[i][j]应该取这两种选择中的较大值。由此得到状态转移方程:

  • 如果j < w[i](背包容量装不下物品i):dp[i][j] = dp[i-1][j]
  • 否则(能装下):dp[i][j] = max(dp[i-1][j], v[i] + dp[i-1][j - w[i]])

这个方程是动态规划解决0-1背包的核心,它清晰地刻画了每个决策点的最优选择是如何从更小的子问题构建而来的。

5.2 从基础实现到空间优化

我们先给出最直观的二维DP实现,并详细注释每一步。

def knapsack_01_DP(weights, values, capacity): """ 使用二维动态规划解决0-1背包问题 :param weights: 物品重量列表,长度n :param values: 物品价值列表,长度n :param capacity: 背包总容量W :return: 能获得的最大价值 """ n = len(weights) # 初始化DP表,多一行一列用于边界条件(考虑0个物品或容量为0) # dp[i][j] 表示考虑前i个物品(1-indexed),容量为j时的最大价值 dp = [[0 for _ in range(capacity + 1)] for _ in range(n + 1)] # 构建DP表 for i in range(1, n + 1): # i 对应物品索引(1到n) w_i = weights[i-1] # 第i个物品的重量(0-indexed调整) v_i = values[i-1] # 第i个物品的价值 for j in range(1, capacity + 1): # j 表示当前背包容量 if j < w_i: # 当前容量装不下第i个物品,只能选择不拿 dp[i][j] = dp[i-1][j] else: # 容量足够,可以选择拿或不拿,取最大值 dp[i][j] = max(dp[i-1][j], # 不拿 v_i + dp[i-1][j - w_i]) # 拿 # 最终结果存储在 dp[n][capacity] max_value = dp[n][capacity] return max_value, dp # 示例运行(使用之前让贪心算法失败的例子) if __name__ == "__main__": weights = [10, 20, 30] values = [60, 100, 120] capacity = 50 max_val, dp_table = knapsack_01_DP(weights, values, capacity) print(f"最大价值为:{max_val}") # 输出应为 220 # (可选)打印DP表以理解过程 print("\nDP表(dp[i][j]):") for i in range(len(dp_table)): print(dp_table[i])

运行后,我们会得到最大价值220,并且可以通过DP表看到计算过程。二维DP的时间复杂度是O(n * W),空间复杂度也是O(n * W)。其中n是物品数量,W是背包容量。注意,这里的复杂度不是关于输入规模n的多项式,而是关于容量W的(W是一个数值),所以当背包容量非常大时,这种算法可能会很慢。这就是背包问题被称为“弱NP完全”的原因。

在实际应用中,W往往不会大到离谱,所以DP解法非常实用。为了节省空间,我们还可以进行优化。观察状态转移方程,dp[i][j]只依赖于dp[i-1][...],即上一行的数据。因此,我们可以只使用一个一维数组dp[j]来表示“当前考虑完某个物品后,容量为j的最大价值”。但遍历顺序需要从右向左(从W到0),以确保在计算dp[j]时,dp[j - w_i]还是“上一轮”(即考虑前i-1个物品时)的值,没有被本轮更新覆盖。

def knapsack_01_DP_optimized(weights, values, capacity): """ 使用一维数组(空间优化)的动态规划解决0-1背包问题 """ n = len(weights) # 初始化一维DP数组,dp[j]表示容量为j的背包能获得的最大价值 dp = [0] * (capacity + 1) for i in range(n): # 遍历每个物品 w_i = weights[i] v_i = values[i] # 关键:必须从右向左遍历容量 # 如果从左向右,dp[j-w_i]可能已经被本轮的物品i更新过,导致物品被重复拿取(这变成了完全背包问题) for j in range(capacity, w_i - 1, -1): # 对于每个容量j,选择:不拿当前物品(dp[j]) 或 拿当前物品(v_i + dp[j - w_i]) dp[j] = max(dp[j], v_i + dp[j - w_i]) return dp[capacity] # 测试优化后的算法 weights = [10, 20, 30] values = [60, 100, 120] capacity = 50 max_val_opt = knapsack_01_DP_optimized(weights, values, capacity) print(f"空间优化后计算的最大价值:{max_val_opt}") # 输出 220

空间优化将空间复杂度从O(n*W)降低到了O(W),这是一个巨大的提升,尤其是当物品数量n很大时。从右向左遍历是理解这个优化版本的关键,务必牢记。

5.3 重构最优解:找出拿了哪些物品

DP算法告诉我们最大价值是多少,但有时我们还需要知道具体选择了哪些物品。这可以通过回溯DP表来完成。我们从最终状态dp[n][W]开始,倒推每一个决策。

def trace_solution(weights, values, capacity, dp): """ 根据完整的二维DP表,回溯找出被选中的物品 :param dp: 二维DP表,由 knapsack_01_DP 函数返回 :return: 被选中物品的索引列表(0-indexed) """ n = len(weights) selected_items = [] j = capacity for i in range(n, 0, -1): # 从最后一个物品倒推到第一个 # 如果 dp[i][j] 不等于 dp[i-1][j],说明第i个物品被选中了 if dp[i][j] != dp[i-1][j]: selected_items.append(i-1) # 记录物品索引(转回0-indexed) j -= weights[i-1] # 从剩余容量中减去该物品的重量 selected_items.reverse() # 反转列表,使物品顺序为正序 return selected_items # 结合之前的二维DP函数使用 max_val, dp_table = knapsack_01_DP(weights, values, capacity) selected = trace_solution(weights, values, capacity, dp_table) print(f"最大价值 {max_val} 对应的物品选择(索引): {selected}") print("具体物品:") for idx in selected: print(f" 物品{idx}: 重量{weights[idx]}, 价值{values[idx]}")

输出将会是:

最大价值 220 对应的物品选择(索引): [1, 2] 具体物品: 物品1: 重量20, 价值100 物品2: 重量30, 价值120

这证实了最优解是拿物品B和C(索引1和2),而不是贪心算法选择的A和B。

踩坑实录:在初学动态规划解背包问题时,最容易混淆的就是遍历顺序。在二维DP中,先遍历物品还是先遍历容量都可以,只要逻辑正确。但在空间优化的一维DP中,遍历物品的外层循环和遍历容量的内层循环顺序不能颠倒,且内层循环必须从大到小遍历容量。如果内层从小到大遍历,就变成了“完全背包问题”(每种物品无限件)的解法,会导致物品被重复选取,得到错误结果。这是一个必须通过动手调试才能深刻理解的细节。

6. 贪心与动态规划的对比与选型思考

通过分数背包和0-1背包的详细剖析,我们可以清晰地看到贪心算法和动态规划在不同问题特性下的表现。

贪心算法(分数背包)

  • 核心:基于价值密度排序的局部最优选择。
  • 前提:问题具备贪心选择性质(物品可分割)。
  • 效率:极高,O(n log n),主要开销在排序。
  • 结果:保证得到全局最优解。
  • 思维模式:直观、简单,一步永逸,无后效性。

动态规划(0-1背包)

  • 核心:定义状态和状态转移方程,通过填表逐步构建最优解。
  • 前提:问题具备最优子结构,且子问题重叠。
  • 效率:O(n * W),效率取决于背包容量W。当W很大时可能较慢。
  • 结果:保证得到全局最优解。
  • 思维模式:系统化、分阶段决策,记录历史信息以避免重复计算。

在实际开发或面试中,如何快速选型?我个人的经验是问自己三个问题:

  1. 物品可否分割?如果是(如液体、散装货物),优先考虑贪心(分数背包)。
  2. 决策是否是二元的?如果是(如拿/不拿,做/不做),且问题规模不大,考虑动态规划。
  3. 问题是否有明显的“排序”或“优先级”性质?如果能证明“每次选当前最好的,最终结果就是最好的”,那么贪心是首选。但证明往往不简单,0-1背包就是一个反例。

对于0-1背包,动态规划是标准解法。但如果物品数量n很大,而单个体积和价值都很小,使得总容量W相对巨大,O(nW)的DP可能不可行。这时可能需要考虑其他方法,如基于分支限界法的搜索,或者对于特别大的n,使用启发式算法或近似算法来寻找一个可接受的解。但无论如何,理解标准的DP解法是应对此类优化问题的基石。

7. 从理论到实践:常见变体与场景延伸

理解了这两个基本模型,我们就能触类旁通,解决许多变体问题。关键在于识别问题本质是否可归约到背包模型。

1. 子集和问题: 这是0-1背包的一个特例,即物品的价值等于其重量(v_i = w_i)。问题变为:是否存在一个物品子集,其总重量恰好等于目标容量W?或者求不超过W的最大重量。解法依然是动态规划,状态dp[j]可以定义为是否存在和为j的子集(布尔型),或者能凑出的不超过j的最大和。

2. 完全背包问题: 每种物品有无限件可用。这更接近现实中的原材料采购。解法依然是动态规划,但状态转移方程变了:dp[j] = max(dp[j], dp[j - w_i] + v_i)。注意,正是因为每种物品无限,所以在空间优化的一维DP中,内层循环的容量j需要从小到大遍历,这与0-1背包的从大到小遍历正好相反,允许同一物品被多次选取。

3. 多维费用背包: 背包的限制不止重量一种,还有体积、时间等第二维、第三维限制。例如,在游戏中,角色装备有重量和空间两个限制。解法是将DP数组扩展到二维或三维,dp[j][k]表示在重量限制j和体积限制k下的最大收益。状态转移需要同时考虑多个维度的消耗。

4. 分组背包: 物品被分为若干组,每组内的物品互斥,最多只能选一个。这类似于从多个分类中各选一个商品。解法是加一层循环,对每一组,用0-1背包的思想在该组物品中做选择。

5. 依赖背包(树形DP): 物品间存在依赖关系,如“要选儿子必须先选父亲”。这通常需要将问题转化为在依赖树(常为二叉树)上的动态规划,是背包问题与树形DP的结合,难度较大,但框架清晰。

在实际编程面试或竞赛中,背包问题常常不会直接以“背包”的面目出现。例如,“给定一个正整数数组,判断是否能分成两个和相等的子集”就是子集和问题。“用几种面额的硬币凑出某个金额,求最少硬币数”是完全背包问题(求最小价值)。识别出这些模型,就能快速套用或修改相应的状态定义和转移方程。

我在处理一个资源配额分配的系统时,就遇到了一个变种的多维背包问题。我们需要将不同类型的计算任务(各有CPU、内存消耗和收益)调度到一台拥有固定CPU和内存的服务器上,最大化总收益。这本质上就是一个二维费用的0-1背包问题。直接套用二维DP模板,将dp[c][m]定义为在c单位CPU和m单位内存下的最大收益,很快就解决了核心的调度算法。关键在于抽象出“物品”(任务)的“费用”(资源消耗)和“价值”(收益),以及“背包”的“容量”(总资源)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询