0-1背包问题

假设有一个承重量为C的背包。现有n件物品,质量分别为w1,w2,…,wn,价值分别为v1,v2,…,vn,求让背包里装入的物品具有最大的价值总和的物品子集。和矿工挖矿问题类似,在选择装入物品时,对每种物品只有装入或者不装入两种选择。不能将同一个物品装入背包多次,也不能只装入该物品的一部分。

设物品i的质量为wi,价值为vi,1≤i≤n,则已知wi>0,vi>0, C>0。求n元向量{x1,x2,…,xn},其中,xi∈{0,1},0表示不选择该物体,1表示选择该物体。

可以把前i个物品中能够放进承重量为j的背包中的子集分为两种类别:

包括第i个物品的子集和不包括第i个物品的子集。

于是可以得到以下结论:

在不包括第i个物品的子集中,最优子集的价值是F(i-1,j);

在包括第i个物品的子集中(j-wi≥0)​,最优子集是由该物品和前i-1个物品中能够放进承重量为j-wi的背包的最优子集组成。这种最优子集的总价值等于vi+F(i-1,j-wi)。

问题实例:设书包的最大承重量C=8

质量价值
参数词典篮球球拍书本
w2453
v5462

初始边界条件:

状态转移函数:

初始条件
i/j012345678
0000000000
10
20
30
40

背包空间为m,设第i个物品所需的空间为w[i],物品价值v[i]。在到达本物品需要空间前,价值等于前i-1个物品,F(i-1,j)
1,放弃第i个物品,总价值为i-1个物品,F(i-1,j)
2,从j空间中分出w[i]去处理第i个物品,总价值为缺了w[i]个空间的前i-1个物品的价值+第i个物品的价值,F(i-1,j-L[i])+v[i]
F(i,j)=F(i-1,j) (i>1,j<w[i])
F(i,j)=max{F(i-1,j),F(i-1,j-w[i])+v[i]} (i>1,j≥w[i])

当只放第一个物品时,w(1)=2、v(1)=5。

第一个结果
i/j012345678
0000000000
1005555555
20
30
40

加上第二个物品,w(2)=4、v(2)=4

第二个结果
i/j012345678
0000000000
1005555555
2005555999
30
40

加上第三个物品,w(3)=5、v(3)=6

第三个结果
i/j012345678
0000000000
1005555555
2005555999
300555691111
40

加上第四个物品,w(4)=3、v(4)=2

i/j012345678
0000000000
1005555555
2005555999
300555691111
400555791111

代码:

def backpack_record(n, c, w, v):
    # 初始化记录表
    backpack_rec = [[0 for _ in range(c + 1)] for _ in range(len(n) + 1)]
    for i in range(1, len(n) + 1):
        for j in range(1, c + 1):
            # 使用状态转移函数填写记录表
            if j < w[i - 1]:  # 承重量不足,放弃第i个物品
                backpack_rec[i][j] = backpack_rec[i - 1][j]
            else:
                backpack_rec[i][j] = max(backpack_rec[i - 1][j], backpack_rec[i - 1][j-w[i - 1]] + v[i - 1])
    return backpack_rec

在确定最优解之后,还需要确定是哪些选择构成了最优解。

根据状态转移函数,可以得到以下回溯方法。

(1)若F(i,j)=F(i-1,j),则说明当前物品没有放到背包中,回溯到F(i-1,j)中。

(2)若F(i,j)=vi+F(i-1,j-wi),则说明当前物品已经放到背包中,将该物品记录为组成最优解的元素,回溯到F(i-1,j-wi)。

(3)重复上述回溯过程,直到i=0,即可获得所有组成最优解的物品集合。

代码:

def backpack_results(n, c, w, res):
    print('可容纳最大价值为:', res[len(n)][c])
    x = [False for _ in range(len(n) + 1)]  # 判断是否装入
    j = c  # 背包承重量
    i = len(n)  # 物品数
    # 回溯
    while i >= 0:
        if res[i][j] > res[i - 1][j]:  # 大于同样承重i-1个物品,说明第i个物品有放入
            x[i] = True  # 确定装入
            j -= w[i - 1]  # w是从0开始的,去掉第i个物品的重量
        i -= 1  # 向下搜索
    print('选择的物品为:')
    for i in range(len(n) + 1):
        if x[i]:
            print('第', i, '个,', end='')
    print('')

输入:

输出:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

algorithm6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值