【面试手撕算法】三种背包问题求解

66 阅读3分钟

背包问题(Knapsack Problem)是一个经典的优化问题,通常描述为给定一组物品,每个物品有重量和价值,要求将这些物品放入一个背包中,背包有一个最大承重限制,目标是使得背包中物品的总价值最大。

背包问题有不同的变种,最常见的有0/1背包问题、完全背包问题和分数背包问题。

1. 0/1背包问题

0/1背包问题是最经典的背包问题。在这个问题中,每个物品只能选择放入背包中,或者不放入背包中,即每个物品的选择是“0”或“1”,不可分割。

问题描述:

  • 给定n个物品,每个物品有一个重量 w[i] 和一个价值 v[i],背包的容量为 W
  • 目标是选择物品,使得放入背包中的物品的总价值最大,并且总重量不超过背包的最大容量。

动态规划解法:

定义 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[n][W],即前n个物品中,总重量不超过W的最大价值。

代码示例:

def knapsack_01(weights, values, W):
    n = len(weights)
    dp = [[0] * (W + 1) for _ in range(n + 1)]
    
    for i in range(1, n + 1):
        for w in range(W + 1):
            if w >= weights[i - 1]:
                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][W]

# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_01(weights, values, W))  # 输出最大价值

2. 完全背包问题

完全背包问题与0/1背包问题的主要区别在于,每个物品可以选择多次放入背包中,即一个物品的数量不限。

动态规划解法:

定义 dp[j] 为容量为j的背包可以装入的最大价值。初始化 dp[0] = 0,其他值为0。

递推关系:

  • 对每个物品i,遍历容量j,从小到大更新 dp[j]dp[j] = max(dp[j], dp[j - weights[i]] + values[i])

代码示例:

def knapsack_complete(weights, values, W):
    n = len(weights)
    dp = [0] * (W + 1)
    
    for i in range(n):
        for w in range(weights[i], W + 1):
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i])
    
    return dp[W]

# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_complete(weights, values, W))  # 输出最大价值

3. 分数背包问题

分数背包问题允许将物品分割,物品的价值与重量成正比。

贪心算法:

  1. 计算每个物品的价值/重量比 value[i] / weight[i]
  2. 按照价值/重量比从高到低排序物品;
  3. 按顺序将物品放入背包,直到背包容量用完。

代码示例:

def knapsack_fractional(weights, values, W):
    n = len(weights)
    items = [(values[i], weights[i], values[i] / weights[i]) for i in range(n)]
    items.sort(key=lambda x: x[2], reverse=True)  # 按价值/重量比排序

    total_value = 0
    for value, weight, ratio in items:
        if W <= 0:
            break
        if weight <= W:
            W -= weight
            total_value += value
        else:
            total_value += value * (W / weight)
            break

    return total_value

# 示例
weights = [2, 3, 4, 5]
values = [3, 4, 5, 6]
W = 5
print(knapsack_fractional(weights, values, W))  # 输出最大价值

总结:

  • 0/1背包问题:动态规划求解,适用于物品只能选择一次的情况。
  • 完全背包问题:动态规划求解,适用于每个物品可以选择多次的情况。
  • 分数背包问题:贪心算法求解,适用于物品可以分割的情况。

这些算法的复杂度和实际使用时的效率会受到问题规模(如物品数量n、背包容量W)的影响,可以根据实际情况选择合适的解法。