局部最优策略(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
时,样本空间的大小为:
为了实现用动态规划的方法进行背包问题的求解,我们首先来看使用动态规划求解斐波那契数列的问题,关于用动态规划求解斐波那契数列的详细讨论请见 每周一刷——从斐波那契数列到动态规划 。
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)还不明显,当物品较多时,画出其决定树,便会出现大量的重叠子问题,又因为weights
和values
是固定不变的,真正变化的是物品编号以及当前背包还能容纳的物品重量。
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))