算法-背包问题

问题描述

假设我有一个背包,希望在装得下的情况下,尽量装进价值更多的物品。那么我该怎么做呢?

问题抽象

假设背包的容量是m,就假设是4吧

# 表示背包容量4KG
m = 4 

可选装进背包的物品有n个,物品的价值存储在prices里,重量存储在weight里

# 表示有3个物品可以选,分别价值是1500 3000 2000
prices = [1500, 3000, 2000]
# 物品重量分别是1kg 4kg 3kg
weight = [1, 4, 3]

显然,我们的目的是填满以下价值矩阵,res[i][j]表示在可选物品有i个的情况下,背包容量在j容量的情况下的最优解:

# 我们需要填满这个n 行 m列的矩阵
res = [[0] * m for _ in range(n)]
物品价值和重量物品\背包容量1234
1500元 1kg物品1
3000元 4kg物品1、物品2
2000元 3kg物品1/2/3

求解过程

显然,第一行是最好求解的。当只有1个物品可选时,只要装得下,只能装它。所以第一行全是1500元。

物品价值和重量物品\背包容量1234
1500元 1kg物品11500150015001500
3000元 4kg物品1、物品2
2000元 3kg物品1/2/3

当可选物品变成2个时,显然,由于物品2的重量是4KG,背包容量小于4的情况下,全都放不下,于是只有在背包容量达到4的时候,可以放一个物品2,这个格子的最优解是3000

物品价值和重量物品\背包容量1234
1500元 1kg物品11500150015001500
3000元 4kg物品1、物品21500150015003000
2000元 3kg物品1/2/3

当可选物品变成3个的时候,在背包容量3的时候,可以选择物品3了,所以这一个格子的值是2000

物品价值和重量物品\背包容量1234
1500元 1kg物品11500150015001500
3000元 4kg物品1、物品21500150015003000
2000元 3kg物品1/2/3150015002000

最后一个格子比较特殊,我们来分别考虑

  1. 如果背包容量-当前最新物品的重量>0,说明还能装其他东西,所以这一个格子可能值是:

    当前物品价值+ 上一行的 剩余背包容量最优解。再拿这个值,和上一行的该列比较,大的那个就是最优解

    # 注意这里j+1,是因为是j从0开始,而背包容量从1开始
    prices[i] + res[i-1][j+1-weight[i]]
    # 
    res[i][j] = max(res[i-1][j], prices[i] + res[i-1][j+1-weight[i]])
    
  2. 如果背包容量-当前最新物品的重量=0,那么说明这时候只能装它了。最优解就是新物品重量和上一行比,大的那一个

    res[i][j] = max(res[i - 1][j], prices[i])
    
  3. 如果背包容量-当前最新物品的重量<0,那么说明新物品对这个格子没有影响,还是取上一行的值

    res[i][j] = res[i-1][j]
    

经过一番比较,我们可以知道这个格子最优是2000加上上一行的容量为1时的值

物品价值和重量物品\背包容量1234
1500元 1kg物品11500150015001500
3000元 4kg物品1、物品21500150015003000
2000元 3kg物品1/2/31500150020003500

最终代码

经过上面的举例,可以看出,大部分格子都遵循这三种情况。除了第一行,因为没法和上一行比较。最终代码如下:

def max_bag(n: int, m: int, weight: list[int], prices: list[int]):
    """
    背包问题
    :param n: 可选物品总数量,矩阵的行
    :param m: 背包总重量,矩阵的列
    :param weight: 物品重量列表
    :param prices: 物品价格列表
    :return: 返回最大价值
    """
    res = [[0] * m for _ in range(n)]

    # 第一行
    for j in range(m):
        if j + 1 - weight[0] >= 0:
            res[0][j] = prices[0]
        else:
            res[0][j] = 0

    for i in range(1, n):
        for j in range(m):
            # 大于零说明装了当前商品之后还有空位,那么最大值就是在res[i-1][j]和
            #  当前价值prices[i] + 剩余格子价值
            # 这两者之间取最大值
            if j + 1 - weight[i] > 0:
                res[i][j] = max(res[i-1][j],  prices[i] + res[i-1][j+1-weight[i]])
            # 等于0,说明刚好能装这一个物品,所以最大值是要么上一行的这个格子,要么就是当前商品价值
            elif j + 1 - weight[i] == 0:
                res[i][j] = max(res[i - 1][j], prices[i])
            # 如果小于0,那就还是取上一行的
            else:
                res[i][j] = res[i-1][j]
    print(res)
    return res[n-1][m-1]

可以用如下输入来验证结果:

my_n = 3
my_m = 4
my_weights = [1, 4, 3]
my_prices = [1500, 3000, 2000]
print(max_bag(my_n, my_m, my_weights, my_prices))

一些优化

考虑到可以让列的索引,也就是j直接代表背包的容量,我们在创建矩阵时,可以直接创建一个n+1 * m+1的矩阵,也就是第一列和第一行全为0

    # 建立一个 n+1 * m+1的矩阵,这样res[i][j]就代表:第i-1个物品在背包容量为j的时候的最优解
    res = [[0] * (m+1) for _ in range(n+1)]

这样做,有3个好处:

  1. 不需要单独处理第一行了,第一行已经有了,并且全为0
  2. j可以直接代表背包容量,不需要减1
  3. j - weight[i-1] >=0的情况,可以合并起来写,这是因为 prices[i-1] + res[i-1][j-weight[i-1]]
    实际上右边会取到第一列的值, res[i-1][j-weight[i-1]]=0,相当于max(res[i-1][j], prices[i-1])

优化后的代码如下:注意i是从1开始的,所以使用weight[i-1] 和 prices[i-1]

def max_bag(n: int, m: int, weight: list[int], prices: list[int]):
    """
    背包问题
    :param n: 可选物品总数量,矩阵的行
    :param m: 背包总重量,矩阵的列
    :param weight: 物品重量列表
    :param prices: 物品价格列表
    :return: 返回最大价值
    """
    # 建立一个 n+1 * m+1的矩阵,这样res[i][j]就代表:第i个物品在背包容量为j的时候的最优解
    res = [[0] * (m+1) for _ in range(n+1)]

    for i in range(1, n+1):
        for j in range(1, m+1):
            if j - weight[i-1] >=0:
                res[i][j] = max(res[i-1][j], prices[i-1] + res[i-1][j-weight[i-1]])
            else:
                res[i][j] = res[i-1][j]
    print(res)
    return res[n][m]

本文参考了:https://blog.csdn.net/bohu83/article/details/91453227

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值