01背包问题

回溯思想解 01背包 问题

基础问题 —— 最大重量

已知:有 n 个物品需要装进背包,items[i] ( 0<= i <= n) 为第 i 个物品的重量 ,背包最大容量为 w。
求:背包最多可装入物品的总重量。
例如, 有 3 个物品,重量分别为 [3, 5, 8], 背包最大容量为 9, 则最大装入物品的总重量为 8,装入方案有 (3, 5) 或 (8), n n n 个物品则具有 2 n 2^n 2n 种可能的装法(每个物品都有装入或者不装入两种状态),剪枝后会降低一定的复杂度。

# 基本 0-1 背包问题,回溯 + 剪枝
class Package:
    def __init__(self):
        self.max_weight = 0 # 保存计算结果

    # 计算给定条件下的能装入背包的物品最大重量。
    # 输入:i -> int,当前物品编号
    #      cw -> int, 当前背包内物品总重量
    #      items -> list[int], 物品重量数组
    #      w -> int: 背包最大容量
    # 输出:无
    def calculate_max_weight(self, i: int, cw: int, items: list, w: int):
        n = len(items)
        if i == n or cw == w: # 物品遍历完 or 背包容量满
            if cw > self.max_weight:
                self.max_weight = cw
            return

        self.calculate_max_weight(i + 1, cw, items, w) # 不放入当前物品
        if cw + items[i] <= w: # 重量超过 w 则做剪枝
            self.calculate_max_weight(i + 1, cw + items[i], items, w) # 放入当前物品

if __name__ == '__main__':
    items = [3, 5, 8, 9]
    pack = Package()
    pack.calculate_max_weight(0, 0, items, 23)
    print(pack.max_weight)

上述问题可以通过一个 flag[n][weight + 1] 来标记重复状态, 对于已经计算过的状态可以直接 return,没计算过的进行计算然后更新 flag 数组。

基础问题变体 1——最大价值

在基础问题上附加条件,每个物品价值不同,如何在不超过背包最大重量的情况下使得装入背包得物品价值最大。
每个物品具有(i, v, w)三个属性,i 为标号、v 为价值, w 为重量。package_volume 为背包容量。

解法如下,只需要对上述基本程序做几点修改 。

  1. 增加物品类 Item(也可以用字典),每个 Item 类对象包括 value 和 weight 两个属性。
  2. Package 类中的计算最大重量部分做修改,具体见 calculate_max_valuecalculate_max_weight 的不同。
  3. 测试用例如下表,其中第一行输入为背包最大容量,后面每行输入为 value weight, 表示每件物品的价值和重量。
测试用例1测试用例2测试用例3测试用例4
20304050
3 58 98 1015 25
4 73 75 79 16
2 810 918 213 2
1 62 87 58 8
8 33 63 94 6
  1. 对应输出如下表,输出为不超过最大容量下,最大价值
测试用例1测试用例2测试用例3测试用例4
输出15213332
# 物品类,每个物品包含 value 和 weight 两个属性
class Item:
    def __init__(self, value: int, weight: int):
        self.value = value
        self.weight = weight

# 基础背包问题变体——最大价值
class Package:
    def __init__(self):
        self.max_value = 0 # 保存计算结果

    # 计算给定条件下的能装入背包的物品最大重量。
    # 输入:i -> int,当前物品编号
    #      cv -> int, 当前背包内物品总价值
    #      items -> list[Item], 物品数组
    #      w -> int: 背包最大容量
    # 输出:无
    def calculate_max_value(self, i: int, cw: int, cv: int, items: list, w: int):
        n = len(items)
        if i == n or cw == w: # 物品遍历完 or 背包容量满
            if cv > self.max_value:
                self.max_value = cv
            return

        self.calculate_max_value(i + 1, cw, cv, items, w) # 不放入当前物品
        if cw + items[i].weight <= w: # 若将当前物品装入,重量不超过 w 则继续,超过的则剪枝
            self.calculate_max_value(i + 1, cw + items[i].weight, cv + items[i].value,items, w) # 放入当前物品

if __name__ == '__main__':
    items = []
    package_volume = int(input())
    while True:
        try:
            value, weight = map(int, input().split())
        except:
            break
        item = Item(value, weight)
        items.append(item)

    pack = Package()
    pack.calculate_max_value(0, 0, 0, items, package_volume)
    print(pack.max_value)

动态规划解背包问题

设置一个 states[n][volume + 1] 数组, 初始化为全 0 0 0 n n n 为物品个数, v o l u m e volume volume 为背包最大容量。states[i][j] 保存的是第 i i i 个物品,总重量为 j j j 时背包内物品价值最大的状态。对于 i > 0 i > 0 i>0 有:
s [ i ] [ j ] = { m a x ( s [ i − 1 ] [ j − w [ i ] ] + v [ i ] , s [ i − 1 , j ] ) ( j − w [ i ] ≥ 0 ) s [ i − 1 ] [ j ] ( j − w [ i ] < 0 ) s[i][j]=\left\{ \begin{aligned} & max(s[i - 1][j - w[i]] + v[i], s[i - 1, j]) & &(j - w[i] \geq 0)\\ & s[i - 1][j]& &(j - w[i] < 0) \end{aligned} \right. s[i][j]={max(s[i1][jw[i]]+v[i],s[i1,j])s[i1][j](jw[i]0)(jw[i]<0)
其中 s s s 表示 states 数组, v v v w w w 分别表示价值和重量数组。

# 动态规划解 01背包 问题
# 物品类 Item
class Item:
    def __init__(self, value, weight):
        self.value = value
        self.weight = weight

# 背包类 Package
class Package:
    def __init__(self, volume):
        self.volume = volume
        self.max_value = 0

    # 动态规划解决 01背包 问题
    # 输入:items 物品数组,每个物品包含 value, weight 属性
    # 输出:无
    def calculate_max_value(self, items):
        n = len(items) # 物品个数
        weights = []
        values = []
        for item in items: # 获取 values 和 weights 数组
            values.append(item.value)
            weights.append(item.weight)

        # 状态数组 states[n][volume + 1]
        states = [[0 for j in range(self.volume + 1)] for i in range(n)]
        states[0][0] = 0 # state[0][i] 要单独处理
        if weights[0] <= self.volume:
            states[0][weights[0]] = values[0]

        for i in range(1, n): # 动态规划移动
            for j in range(weights[i]):
                states[i][j] = states[i - 1][j]
            for j in range(weights[i], self.volume + 1):
                states[i][j] = max(states[i - 1][j], states[i - 1][j - weights[i]] + values[i])

        self.max_value = max(states[n - 1]) # 更新 package 装入物品的最大价值

if __name__ == '__main__':
    items = [] # 物品列表
    package_volume = int(input()) # 背包最大容量
    while True:
        try:
            value, weight = map(int, input().split())
        except:
            break
        item = Item(value, weight)
        items.append(item)

    pack = Package(package_volume)
    pack.calculate_max_value(items)
    print(pack.max_value)
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值