问题建模
所以如果以后你见到长这样的东西
$$ {\rm maximize} \ 16 x_1 + 19 x_2 + 23 x_3 + 28 x_4 \\
{\rm subject \ to} \ 2 x_1 + 3 x_2 + 4 x_3 + 5 x_4 \le 7, \\
\forall i \in 1..4, \ x_i \in \{ 0, 1 \} $$
这就是一个 0-1 背包问题。
问题规模
前提:$K, w_i$ 是整数。
数学递归方案
表示:$O[k, n]$ 表示在已经携带 $k$ 重量物品,物品集合为 $I \cap[1, n]$ 时的最优目标函数值。
起始条件:$\forall k, O[k, 0] = 0$。
终止条件:我们的目标是求解出 $\forall k, O[k, N]$,再通过作 $\max_{k=0}^K O[k, N]$ 就是最优解。
递归方案:
注:数学上更倾向于「人人为我」的递归模式,因而以要求解的目标对象为主。如果我们是知道 $n$ 的想要「去求」$n+1$ 的,并认为 $k$ 是旧容量「来去求考虑 $n+1$ 对象后的新容量」,就是这样的「我为人人」模式:
- 假设我们已经知道了 $\forall k, O[k, n]$,想要求 $\forall k, O[k, n+1]$,那么我们只需要多考虑第 $n+1$ 个物体。对于每个放置前的容量 $k$:
- 如果 $k + w_{n+1} > K$,物体肯定放不进去了,因而 $O[k, n+1] = O[k, n]$。
- 如果 $k + w_{n+1} \le K$,可以选择放物体或者不放物体:
- 如果不放物品就还是 $O[k, n+1] = O[k, n]$;
- 如果放物品就是 $O[k+ w_{n+1}, n+1] = O[k, n] + v_{n+1}$;
- 这是一个「我为人人」顺序模式,只要保证登记的总是最大值即可。
Top-down 递归实现:
def O(k, n):
if n == 0:
return 0
elif k < w[n]:
return O(k, n-1)
else:
return max(O(k, n-1), O(k-w[n], n-1) + v[n])
Bottom-up 计算:逐渐建立一个二维表 $O[k, n]$。
Trace-back:如果和左边的数相同,就是没选,如果不同,就是选了。
Complexity:$O(K \cdot N)$
0-1 背包问题的可行树如下。
branching 分支: split the problem into a number of subproblems, like in exhaustive search
bounding 限界: find an optimistic estimate of the best solution to the subproblem
比如在搜索中,如果遇到了「价值上界」(目前已经确认选择的价值 + 尚未确定选择的物体总价值,如下图蓝色)已经比曾经到手过的最优价值(如下图绿色)更少的情况,就可以直接剪枝。
排序:实践发现,将物品先按性价比排序,然后先走「拿物体」的选项,DFS 求解速度能快一倍以上。这是因为一般性价比更高的物体在结果中确保被选到,放在前面就能更早求出更大的最优价值,增加剪枝的频率。