0-1背包问题及python实现

0-1背包问题及python实现

1. 问题提出

​ 0-1背包问题是动态规划中入门的经典题型,掌握0-1背包问题背后的本质有助于更好地理解动态规划问题,话不多说,首先来看看0-1背包问题究竟是什么吧~

问题描述:设有n件物品x1, x2, …, xn,每件物品有一个价值和一个重量,分别记为v1, v2, …, vn和w1, w2, …, wn,其中所有的wi均为整数。现有一个背包,其最大载重量为m,要求从这n件物品中任取若干件(这些物品要么被装入要么被留下)。问背包中装入哪些物品可是得所装物品的价值和最大?

样例1输入:m=11, n=6

物体编号i 0 1 2 3 4 5
物体重量wi 2 4 5 6 10 3
物体价值vi 1 7 4 5 11 1

样例1输出:12

2. 递归实现暴力枚举

​ 在不考虑时间的情况下,暴力枚举是一个很直观的方法,也就是枚举所有可能装入背包的物品组合,找出价值和最大的组合,只不过这里我们是用递归的方式来实现这个枚举。

​ 我们首先定义递归函数backpack(i, w),它表示在0至i号物品中,载重量为w时的所有可行方案的最大价值和,那么很容易得到递归关系式:backpack(i, w) = max{backpack(i - 1, w - wi) + vi, backpack(i - 1, w)},也就是在0至i号物品中,载重量为w的最大价值和等于0至i-1号物品中,载重量为w-wi的最大价值和加上vi的值0至i-1号物品中,载重量为w的最大价值和中的较大值,python代码如下。

def backpack(i, w):
    global weight, value
    if w < 0:
        return -float("inf")
    if i < 0:
        return 0
    return max(backpack(i-1, w-weight[i])+value[i], backpack(i-1, w))


m, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
print(backpack(n-1, m))

3. 带备忘递归

​ 很显然,暴力枚举的复杂度是十分高的,那么我们可以如何去优化这个暴力枚举的复杂度呢?我们从一种简单的特殊情况来分析,假设0至n号物品的重量均为c,那么我们可以得到以下的递归树。

在这里插入图片描述

​ 很显然,在经过2层递归后,就出现了重复的子问题backpack(n-2, w-c),并且继续递归下去,重复的子问题会越来越多。因此,我们可以构造一个备忘录,对于重复求解的问题,计算一次后记入备忘录,下次遇到相同的问题,只需要从备忘录中查询就行。python代码也只需要在原先的基础上做简单的变动即可。

def backpack(i, w):
    global weight, value, p
    if w < 0:
        return -float("inf")
    if i < 0:
        return 0
    if p[i][w]:
        return p[i][w]
    p[i][w] = max(backpack(i-1, w-weight[i])+value[i], backpack(i-1, w))
    return p[i][w]


m, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
p = [[None for j in range(m + 1)] for i in range(n)]
print(backpack(n - 1, m))

4. 动态规划

​ 因为递归毕竟是递归,它的计算过程肯定首先是自顶向下递归到最原始的单一问题,之后再通过自底向上的递推过程,计算每一步的解,最终回归到了最初的问题。那么我是否们能够通过这个备忘录的数组直接自底向上计算呢?

​ 答案当然是可以的,那就是动态规划。我们可以通过递推关系公式p[i, w]=max{p[i-1, w-weight[i]]+value[i], p[i-1, w]}来逐行逐行的填充这个数组,最终数组最右下角的值就是最大价值和。

p[i, w] 0 1 2 3 4 5 6 7 8 9 10 11
0 0 0 1 1 1 1 1 1 1 1 1 1
1 0 0 1 1 7 7 8 8 8 8 8 8
2 0 0 1 1 7 7 8 8 8 11 11 12
3 0 0 1 1 7 7 8 8 8 11 12 12
4 0 0 1 1 7 7 8 8 8 11 12 12
5 0 0 1 1 7 7 8 8 8 11 12 12

​ 现在我们已经得到了最大价值,还有一个问题,那就是到底选择了哪些物品能够获得这个最大价值?我们可以用rec数组记录下决策过程,在填充p[i, j]时,如果选择了i商品,则rec[i, j]=1;反之,rec[i, j]=0。然后我们可以用rec数组来追踪最优解,一行一行往回倒推,当rec[i, j]=1时,选择商品i,考察子问题p[i-1, w-weight[i]];当rec[i, j]=0时,不选商品i,考察子问题p[i-1, c]。

​ 以样例1为例,rec[5, 11]=0,说明没有选择5号商品,则rec[5, 11]是由rec[4, 11]推导而来的;再看rec[4, 11]=0,说明也没有选择4号商品,则rec[4, 11]是由rec[3, 11]推导而来的;再看rec[3, 11]=0,说明也没有选择3号商品,则rec[3, 11]是由rec[2, 11]推导而来的;再看rec[2, 11]=1,说明选择了2号商品,则rec[2, 11]是由rec[1, 11-weight[2]]即rec[1, 6]推导而来的;再看rec[1, 6]=1,说明也选择了1号商品,则rec[1, 6]是由rec[0, 6-weight[1]]即rec[0, 2]推导而来的;再看rec[0, 2]=1,说明也选择了0号商品。

rec[i, w] 0 1 2 3 4 5 6 7 8 9 10 11
0 0 0 1 1 1 1 1 1 1 1 1 1
1 0 0 0 0 1 1 1 1 1 1 1 1
2 0 0 0 0 0 0 0 0 0 1 1 1
3 0 0 0 0 0 0 0 0 0 0 1 0
4 0 0 0 0 0 0 0 0 0 0 0 0
5 0 0 0 0 0 0 0 0 0 0 0 0

​ 于是,python的代码也就比较容易得到了。

w, n = 11, 6
weight = [2, 4, 5, 6, 10, 3]
value = [1, 7, 4, 5, 11, 1]
p = [[0 for j in range(w + 1)] for i in range(n)]
rec = [[0 for j in range(w + 1)] for i in range(n)]
for j in range(w + 1):
    if weight[0] <= j:
        p[0][j] = value[0]
        rec[0][j] = 1
for i in range(1, n):
    for j in range(w + 1):
        if weight[i] <= j and value[i] + p[i-1][j-weight[i]] > p[i-1][j]:
            p[i][j] = value[i] + p[i-1][j-weight[i]]
            rec[i][j] = 1
        else:
            p[i][j] = p[i-1][j]
print(p[n-1][w])
print("choose item ", end="")
tmp = w
for i in range(n-1, -1, -1):
    if rec[i][tmp] == 1:
        print(i, end=" ")
        tmp -= weight[i]

5. 总结

​ 动态规划算法的本质就是把原始问题分解成子问题,并寻找小问题和大问题之间的关系,从小问题向大问题的规模去扩展,最后达到最优解。总的来说,动态规划算法可以分解为4个步骤:

  • 问题结构分析:描述一个最优解的结构;
  • 递推关系建立:递推地定义最优解的值;
  • 自底向上计算:以“自底向上”的方式计算最优解的值;
  • 最优方案追踪:从已计算的信息中构建出最优解的路径。

​ 在本题中,问题结构分析就是给出0-1背包问题的表现形式,也就是p[n-1, w]表示在0至n-1号物品中,载重量为w时的所有可行方案的最大价值和;递推关系建立需要去分析最优子结构(最优子结构性质就是指问题的最优解由相关子问题最优解组合而成,子问题可以独立求解),在本题中,是否取第i件商品就构成了原问题的两个相关子问题,然后两个相关子问题的最优解的较大值也就是原问题的最优解,于是递推关系也就得到了,p[i, w]=max{p[i-1, w-weight[i]]+value[i], p[i-1, w]};再得到递推关系后,自底向上计算就很容易了,只要在初始化后,根据递推关系公式就可以依次求解问题了;如果需要输出最优方案,那么就需要使用rec数组来追踪最优方案,rec数组记录了决策的过程。

猜你喜欢

转载自blog.csdn.net/NickHan_cs/article/details/107964648