从0-1背包问题到动态规划

算法——贪心算法解0-1背包问题

  • 局部最优策略(locally optimal decisions,比如贪心算法),并不保证全局最优(global optimums)

  • “在我的后园,可以看见墙外有两株树,一株是枣树,还有一株也是枣树。”,同样如果有两个手表,为了达到0/1背包问题的要求,也可转化为一个手表,还有一个手表。

  • brute force(暴力法)的本质是 exhaustive enumeration(穷举法)

  • 一个算法的时间复杂度为指数级时,将会产生十分恐怖的计算量,而动态规划算法可以用来解决指数级时间复杂度的问题,只是说,有些指数级时间复杂度的问题可以通过动态规划算法求解,并非全部。

动态规划(Dynamic programming)核心概念有二:

  • overlapping subproblems: 重叠子问题

    或者说,子问题(subproblems)出现了重叠,子问题的“重叠”意味着计算机的“重复计算”。比如,斐波那契数列的例子不像二分搜索(子问题之间比如独立,也即不存在子问题重叠的问题),可参考 每周一刷——从斐波那契数列到动态规划 。

  • optimal substructure: 最优子结构

    所谓动态规划即是寻找这样的一个最优子结构(substructure),它通过引入 memo 查找表(look-up table)的形式实现对重叠的子问题只进行有限次的计算。也即:record value 1st time,look it up the subsequent times we need it(一次记录,多次使用)

背包问题(knapsack problem)又叫装箱问题(bin packing)。所谓0-1背包问题,对于一件物品要么拿走,要么不拿,不存在拿部分的情况,自然可与二进制对应起来,所谓当物品为 n 时,样本空间的大小为: 2n

为了实现用动态规划的方法进行背包问题的求解,我们首先来看使用动态规划求解斐波那契数列的问题,关于用动态规划求解斐波那契数列的详细讨论请见 每周一刷——从斐波那契数列到动态规划 。

def fib(n, m):
    if n not in m:
        m[n] = fib(n-1, m) + fib(n-2, m)
    return m[n]

if __name__ == '__main__':
    m = {0:0, 1:1}
    print([fib(n, m) for n in range(10)])
        # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

decision tree(决策树)

  • weights = [5, 3, 2],三个物品各自的重量;
  • values = [9, 7, 8],三个物品各自的价值;
  • max = 5,背包的最大承重;

所谓0/1背包问题,也即对每一件物品只有选和不选两种选择,也即分二叉,从当前节点出发,有两个分支。

我们定义如下的树的节点(node)结构:物品的编号背包还能容纳的最大重量当前背包所放物品的价值构成的三元组,我们以逆序遍历每件物品,所以根节点结构为:[2, 5, 0]。



def maxVal(i, w, v, c):
    if i == 0:
        return v[i] if w[i] <= 0 else 0
    without_i = maxVal(i-1, w, v, c)
    if w[i] > c:
        return without_i
    with_i = v[i] + maxVal(i-1, w, v, c-w[i])
    return max(without_i, with_i)

if __name__ == '__main__':
    weights = [5, 3, 2]
    values = [9, 7, 8]
    n, c = len(weights), 5
    print(maxVal(n-1, weights, values, c))

上例中的重叠子问题(overlapping subproblem)还不明显,当物品较多时,画出其决定树,便会出现大量的重叠子问题,又因为weightsvalues是固定不变的,真正变化的是物品编号以及当前背包还能容纳的物品重量。

def fastMaxVal(i, w, v, c, m):
    try:
        return m[(i, c)]
    except KeyError:
        if i == 0:
            if w[i] <= c:
                m[(i, c)] = v[i]
                return m[(i, c)]
            m[(i, c)] = 0
            return 0
        without_i = fastMaxVal(i-1, w, v, c, m)
        if w[i] > c:
            m[(i, c)] = without_i
            return m[(i, c)]
        with_i = v[i] + fastMaxVal(i-1, w, v, c-w[i], m)
        m[(i, c)] = max(with_i, without_i)
        return m[(i, c)]
if __name__ == '__main__':
    ws = [5, 3, 2]
    vs = [9, 7, 8]
    n, c = len(ws), 5
    m = {}
    print(fastMaxVal(n-1, ws, vs, c, m))

你可能感兴趣的:(从0-1背包问题到动态规划)