曹逸君 Blog

6月 17, 2014

看的懂的动态规划解释(动态规划求解0/1背包问题)

网上看到的中文对动态规划的解说以及样例让我很火大。不说清楚总体思路就跳到细节里。给的代码也是用尽没有说明的单字母缩写。不明所以的循环。 而维基你懂得。你要看懂一篇,先得把里面专业名词,各种数学全了解遍。

以0/1背包问题为例。先说明问题。【我就不用背景故事了。钻石还是砖头都无所谓】 有N个物品。每个物品有两个属性。它所占的空间或着说重量weight.和它的价值value。 和一个容器[背包],背包有最大容量Capacity。求此背包能放下的东西的最大价值。

先看下暴力搜索是怎么处理的,把能放进去的物品的每种放法全部穷举一遍。找出最大值。 而贪心则是先放性价比最高的[value/weight最大]。再放其次。依次类推。

动态规划,带着如此高端大气上档次的名字。其实很简单,是做出一张表,得到最大值。 表如下:

物品1   只有物品1能达到的最大值                  
物品2   只有物品1和物品2能达到的最大值                  
物品3   有物品1和物品2和物品3能达到的最大值                   
物品4   有物品1、2、3、4能达到的最大值                 
...                     
物品n                     
        背包容量为1  2   3   4   ... Capacity

动态规划先把只有物品1在不同容量下能达到的最大价值列举出来。 看具体例子,有以下四个物品。3h什么的是weight[或者说cost]。100€是价值(value)。

A 3h 100€
B 5h 120€
C 4h 80€
D 1h 50€

那么在计算第一行,只有A物品的情况后得到的表是这样子的:

A   0   0   100 100 100 100 100 100 
B   0   0   0   0   0   0   0   0   
C   0   0   0   0   0   0   0   0   
D   0   0   0   0   0   0   0   0   
容量 1   2   3   4   5   6   7   8

当然,程序里是没有第一列的ABCD和底下一行的容量的。那是我为了标注清楚加上去的。

第一行。只有物品A。在容量为1和2时,放不下,故最大价值为0。容量3到8只能放A。最大价值为100。

A   0   0   100 100 100 100 100 100 
B   0   0   100 100 120 120 120 220 
C   0   0   0   0   0   0   0   0
D   0   0   0   0   0   0   0   0   
容量 1   2   3   4   5   6   7   8

注意,这里是动态规划的精华部分。

现在计算第二行。此时新加入物品B 5h 120€。

[注意动态规划并不关心之前是几个物品,怎么放的。只要知道上一行(在没加此行新物品时)在不同容量下的最大价值即可。]。

当容量是1到4时,是不可能放下物品B的。故跟没有物品B的最大价值一致。也就是跟上面一格值相同。 当容量是5时,有几种选择。

1.不放B,价值为上面一格,也就是100.

2.放B,价值为: B的价值120 加上 在没有B、容量为0情况下的最大价值[也就是上一行第0列],也就是0。得到120.

当前容量5减掉B的重量(weight)5 得到的0。 选择两种情况种大的,便是容量为5,新加B情况下的最大价值。

我们再来看第二行最后一个220是如何得到的。[显然它是同时放了A和B,但重点是我们要理解动态规划是如何得到这个值的]

此时计算的是容量为8,新加物品B 5h 120€。

1.不放B,价值为上面一格,也就是100.

2.放B,价值为: B的价值120 加上 在没有B、容量为3情况下的最大价值[也就是上一行第3列],也就是100。得到220.

当前容量8减掉B的重量(weight)5 得到的3。 为什么要加上没有B。容量为(当前容量减B重量)的值?因为这样子就可以得到把B放进去得到的最大值了。

重复这一过程就能把整个表完成。而右下那个格子就是我们要求的情况。有n个物品。容量为Capacity所能达到的最大价值。 贴一下中间过程。可以看着思索下。脑中模拟下。

0   0   0   0   0   0   0   0   0   
0   0   0   100 100 100 100 100 100 
0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   

0   0   0   0   0   0   0   0   0   
0   0   0   100 100 100 100 100 100 
0   0   0   100 100 120 120 120 220 
0   0   0   0   0   0   0   0   0   
0   0   0   0   0   0   0   0   0   

0   0   0   0   0   0   0   0   0   
0   0   0   100 100 100 100 100 100 
0   0   0   100 100 120 120 120 220 
0   0   0   100 100 120 120 180 220 
0   0   0   0   0   0   0   0   0   

0   0   0   0   0   0   0   0   0   
0   0   0   100 100 100 100 100 100 
0   0   0   100 100 120 120 120 220 
0   0   0   100 100 120 120 180 220 
0   50  50  100 150 150 170 180 230 

Cars chosen to repair:
D 1h 50€
C 4h 80€
A 3h 100€
Total revenue:
230

多出来的第一行0和第一列0是为了让下标符合题目的语义。容量1就是下标1.物品1就是下标1。

关于代码见https://github.com/c19/java_exercise/tree/master/car_repair

相信看懂后不看代码也能顺利写出动态规划。

误解与选法倒推

请务必牢记价值表的含义。

动态规划在计算每个格子的时候,考虑的都只是那个格子的情况。这个格子的情况会不会被最终解引用。这个元素在最终解中到底会不会被加入。动态规划是不知道的。也不关心。

动态规划的出表后所得到的只是能获得的最大价值。并不是选择方式。然而选择方式可以通过价值表倒推得到。

0   0   0   0   0   0   0   0   0   
0   0   0   100 100 100 100 100 100 
0   0   0   100 100 120 120 120 220 
0   0   0   100 100 120 120 180 220 
0   50  50  100 150 150 170 180 230 

先查看右下角格子。跟它上方的格子比较,如果比上方的大。意味着:背包容量为8.有D物品比没有D物品的最大价值要大。也就是背包容量为8,有D物品的情况下,D物品被放入了。

然后我们把D物品(D 1h 50€)的空间/重量减掉。剩余容量7。不包含D物品。查看C行,第7列。是180,大于它上方的120.同理说明C也被选中了。

把C 4h 80€的重量减掉,剩余容量3.查看B行,第3列。是100。和它上方的100(A,3)相同。故B没有被选入。

这时查看(A,3)100上方的格子,(因为B未选中,我们就可以移动到不包含B的情况了。)。是0.小于100.故A被选中了。

Written with StackEdit.