0/1背包问题--Dynamic Programming

时间:2021-11-16 18:39:23

DP问题的特征:

  1. 重复子问题
  2. 存在最优子集

背包问题属于经典的DP问题,而0/1背包问题是属于最简单的情况。

0/1的意思是每种物品只有一件,要么放入背包中,要么不放

问题定义:

有N个物品,要放入容量为W的背包中,第i件物品重量为w(i),

价值为v(i),问要怎样放才能在不超过背包容量的基础上,获得最大的价值。

算法描述:

需要用到递归的思想,定义A(i, j)为前i个物品中在容量为j的情况下所能

达到的最大价值,则A(0,j) = 0,A(i,0) = 0(i <= N and j <= W).

如果w(i) > j时,说明第i件物品不能放入背包中,价值不变,所以A(i, j) = A(i - 1, j);

如果w(i) < j时,说明第i件物品能入入背包中,那么又有两种情况,选择放入或者是

不放,如果选择不放入,那么价值还是不变,A(i, j) = A(i - 1, j);如果选择放入,

A(i, j) = v(i) + A(i - 1, j - w(i)),到底选择放入还是不放入,需要取这两种选择的最大值。


用数学表达式可以表示为:

0/1背包问题--Dynamic Programming

代码

版本1:

def B(w, v, i, j):
    if i == 0:
        if w[i] <= j:
            return v[i]
        else:
            return 0
    without_i = B(w, v, i - 1, j)
    if w[i] > j:
        return without_i
    else:
        with_i = v[i] + B(w, v, i - 1, j - w[i])
    return max(without_i, with_i)
    
if __name__ == "__main__":
    w = [2, 3, 4, 5, 6]#list of item's weight
    v = [1, 4, 3, 6, 8]#list of item's value
    i = len(w) - 1     #number of items
    j = 12             #max weight constraints
    print B(w, v, i, j)

版本1的实现按照算法的思路可以解决问题但是时间复杂度为指数级,当i取大一点

值时,需要运算很久。这也是一些递归算法存在的问题,这个时候,需要用一种叫做

memorization的技术,也就是储存中间值的方法,纪录之前已经算好的子问题的值,不

用重新计算,直接拿来用就好。

版本二:

m = {}#memo of value of subproblem, m[(i,j)] = maxValue

def fastB(w, v, i, j):
    global m
    try:
        return m[(i, j)]
    except KeyError:
        if i == 0:
            if w[i] <= j:
                m[(i, j)] = v[i]
                return v[i]
            else:
                m[(i, j)] = 0
                return 0
    without_i = fastB(w, v, i - 1, j)
    if w[i] > j:
        m[(i, j)] = without_i
        return without_i
    else:
        with_i = v[i] + fastB(w, v, i - 1, j - w[i])
    res = max(without_i, with_i)
    m[(i, j)] = res
    return res
    
if __name__ == "__main__":
    w = [2, 3, 4, 5, 6]
    v = [1, 4, 3, 6, 8]
    i = len(w) - 1
    j = 12
    print fastB(w, v, i, j)
版本二,因为加入储存中间值的变量B,实际运行中,速度大大提高,时间复杂度接近

线性,用空间换时间的方法。

附加问题:

在求出最大值的同时,需要返回具体的物品最优列表

算法描述:

这个问题需要使用到上一个问题中得到的m字典,从最后一个结果往前算,范围为[n-1,1],当m[(i, j)] > m[(i - 1, j)]时,把物品i放入最优列表中,接着j = j - w(i), 继续往前比,比完之后,最后决定第0个物品是否要加入背包,当最优列表中所有物品重量再加上第0个物品的重量不超过容量限制时,把第0个物品添加到最优列表中,否则不加入

代码:

def findOptimalSub(m, w, i, j):
    res = []
    maxWeight = j
    sum = 0
    
    for index in range(i, 0, -1):
        if m[(index, j)] > m[(index - 1, j)]:
            res.append(index)
            j = j - w[index]
    
    for item in res:
        sum = sum + w[item]
    
    if maxWeight - sum >= w[0]:
        res.append(0)
    return res