动态规划之0-1背包问题

一般提问方式:给定背包容量V和n个物品,每个物品的重量和价值分别为ww[i]v[i]。选择若干物品放入背包,使得总价值最大且总重量不超过V。(白话:一个包只能装一定的物品,如何分配这些空间来装下最多价值的物品,就像高智商小偷进攻博物馆)

动态规划解决 0-1 背包问题的核心思想是利用“阶段性最优解的重叠子问题”,通过构建状态和递推公式,将一个复杂问题分解为多个小问题求解。

1. 问题描述

0-1 背包问题是一个经典的优化问题,要求从 n 个物品中选取一些放入容量为 V 的背包,使得总重量不超过 V 的前提下,总价值最大。

对于每个物品 i:

  • 重量 w[i]
  • 价值 v[i]

选择规则:

  • 每个物品只能选一次(选或不选,即 0 或 1)。
  • 背包的总重量不超过 V。

目标:

\max \sum_{i \in S} v[i]  ,使得 \sum_{i \in S} w[i] \leq V


2. 动态规划的思想

(1) 状态定义

状态是动态规划的核心,用来表示问题的子结构。

定义 f[i][j]表示:

  • 在前 i 个物品中,
  • 背包容量为 j 时,
  • 可以获得的最大价值。

最终目标:

f[n][V]即在考虑所有物品时,背包容量为 V 的最大价值。


(2) 转移方程

动态规划的递推关系基于是否选择当前物品 i:

  1. 不选择第 i 个物品
    最大价值相当于只考虑前 i−1 个物品时的结果,即:

    f[i][j]=f[i-1][j]
  2. 选择第 iii 个物品(前提是 w[i] \leq j):
    最大价值等于前 i−1 个物品在剩余容量j-w[i]时的最大价值,加上当前物品的价值:

    f[i][j]=f[i-1][j-w[i]]+v[i]

综合考虑两种情况,取最大值:

f[i][j] = \max(f[i-1][j], f[i-1][j-w[i]] + v[i]) \quad w[i] \leq j


(3) 边界条件
  1. 当背包容量为 0 时,最大价值为 0: f[i][0] = 0 \quad \forall i
  2. 当没有物品时,最大价值为 0:f[0][j] = 0 \quad \forall j

(4) 状态表的填充

动态规划从小问题开始解决,逐步填充状态表:

  • 行表示考虑的物品数量 i 。
  • 列表示背包容量 j 。

3. 动态规划解决流程

  1. 初始化状态表

    • 构建一个大小为(n+1)×(V+1) 的二维表 f
    • 初始化第一行和第一列为 0(无物品或容量为 0 时最大价值为 0)。
  2. 逐步填表

    • 遍历物品 i 从 1 到 n 。
    • 遍历容量 j 从 1 到 V 。
    • 根据转移方程更新 f[i][j]
  3. 获取最终结果

    • f[n][V] 为最大价值。

4. 示例讲解

示例问题

背包容量 V=8,有 4 个物品:

  • 物品 1:重量 2,价值 3
  • 物品 2:重量 3,价值 4
  • 物品 3:重量 4,价值 5
  • 物品 4:重量 5,价值 6
状态表填充

构建状态表 f[i][j](行表示物品数,列表示容量):

i\j012345678
0(无物品)000000000
1(物品 1)003333333
2(物品 2)003447777
3(物品 3)003457899
4(物品 4)0034578910
  • 最大价值为 10。
  • 最优选择:选择物品 2 和物品 3(总重量为 3+4=7,总价值为 4+6=10)。

5. 时间与空间复杂度

  1. 时间复杂度

    • 遍历 n 个物品,每个物品遍历容量 V。
    • 时间复杂度为 O(n \cdot V)
  2. 空间复杂度

    • 状态表大小为 O(n \cdot V)
    • 优化:可以用一维数组优化空间至 O(V)

6. 总结

  1. 动态规划通过构建状态表和递推公式,避免了暴力枚举所有可能组合的时间浪费。
  2. 递推公式体现了“选择与不选择”的决策过程,确保每个状态的最优解。
  3. 通过自底向上的方式,将复杂问题逐步分解,最终求得全局最优解。
def knapsack(V,items):
    n = len(items)
    print(n)

    f=[]
    for i in range(n+1):
        row = [0]*(V+1)
        f.append(row)

    #w为重量,v为价值,n为物品数量,V为背包容量
    for i in range(0,n):
        for j in range(0,V+1):
            w = items[i][0]
            v = items[i][1]
            if w > j:
                f[i+1][j]=f[i][j]
            else:
                f[i+1][j]=max(f[i][j],f[i][j-w]+v)

    return f[n][V]




if __name__ == '__main__':
    V = int(input("请输入背包容量:"))
    n = int(input("请输入物品数量:"))
    items=[]
    for i in range(n):
        w = int(input(f"第{i+1}个物品的重量:"))
        v = int(input(f"第{i+1}个物品的价值:"))
        items.append((w,v))

    result=knapsack(V,items)
    print(f"背包最大价值为:{result}")

运行实例结果如下:

与手写状态表结果一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值