http://www.wutianqi.com/?p=539
上次研究一个贪心法问题里的最小生成树模型, 现在再来研究一下动态规划, 因为其实二者在我看来是一会儿事儿. 只不过后者递回的概念更为明显吧.
先简单地陈述一下背包问题:
话说有一个贼, 他背着一个包去偷东西. 这个包的容量是W. 他有种东西可以偷(每种东西有无数个), 它们的重量分别是
w1,..,wn
v1,...vn.
而他最多可以拿k个东西.
我们的任务就是帮他最大化他可以偷的东西的总价值.
一.
把这一切变成符号:
已知有n种东西, 分别有重量 w1,..,wn, 价值 v1,...,vn. 又已知最大重量为W, 要如何取得最大价值呢?
二.
所谓的动态规划, 其实就是要把眼前的问题延迟, 小化. 直到小到你一眼就知道答案为止.
这是一个在计算机科学里非常核心的概念. 归纳法和递进都是如此. 举一个简单的例子, 好比说
你要上楼梯, 但是你不确定自己能不能爬到100层. 但是如果我现在给你两个条件.
1) 你可以从第k层上到第k+1层.
2) 你可以从第0层上到第1层.
那么在完美的数学世界里, 你就可以确定你是可以爬到第100层的. (当然在不完美的现实世界里你可能在第99层就选择跳楼自杀)
那么回到背包问题, 我们就要用到这个概念了.
现在来重温一下我们眼前的问题:
"已知有n种东西, 分别有重量 w1,..,wn, 价值 v1,...,vn. 又已知最大重量为W, 要如何取得最大价值呢?"
注意:
最大重量为W.
我们要做的就是要让W变小. 我们把眼前的问题拖延一下, 去看看比她们更小的问题.
所以就有了解决动态规划的第一个步骤:
步骤一: 找出子问题.
在这里, 我们的子问题是:
"已知最大重量为w, 要如何取得最大价值呢?"
好了, 现在我们把解决W的问题成功的延后了, 但是要记得该来的总是会来.
我们定义一个函数, 让它为F(w). 但是我们最终是要返回F(W)的.
步骤二:定义递进关系
在爬楼梯的例子里, 从k层总是能上到k+1层就是一个递进关系, 那么这个问题的递进关系又在哪里呢?
在动态规划里, 递进关系通常都是以选择的形态出现. 换言之, 在第i个递进中, 我们总是假设前i-1的解都是正确的, 我们要在这个假设的基础上决定在第i个递进中做出选择.
于是我们这样定义函数:
F(w)
是在最大重量是w, 贼可以获得的最大价值.
回到背包问题, 就是这样的:
那么现在的总重量就是w,而其中包括了东西i, 那么前一次的总重量就是w-wi, 而加上东西i的价值,我们就有,
F(w)=F(w-wi)+vi
但是这个i是哪个东西呢? 我们不知道. 而我们知道i只需要满足一个条件, 那就是wi<w.
所以我们要把符合条件的都检验一遍, 并且选择最大的
F(w)=max { F(w-wi)+vi: wi<w)}
步骤三: 选择基本状态
也就是最初上楼梯的那一步.
背包问题的基本状态很简单, 那就是当最大重量是0的时候, 你什么也拿不走,
也就是说:
F(0)=0
步骤四: 伪代码
这样一来我们就有了我们需要的所有步骤, 就差最后这么一抽了.
伪代码如下:
F(0)=0
for w=1 to W
F(w)=max { F(w-wi)+vi: wi<=w, if all wi>w return 0}
return F(W)
为了更好地看清动态规划是如何运作的, 我们可以追一个例子来看看.
假如是这样的:
W=10
东西 重量 价值
1 6 30
2 3 14
3 4 16
由于此问题的简单性, 人脑就显示出它的优越性, 我们可以很轻易地看出, 这时候最好的选择是:
1 +3 : 30+16=46
F(w)=max { F(w-wi)+vi: wi<=w, if all wi>w return 0}
而电脑呢, 它依旧是那么傻,
F(0)=0
w=1
F(1)=0,
F(2)=0,
F(3)=F(0)+14=14,
F(4)=max{F(1)+14, F(0)+16}=max{14,16}=16
F(5)=max{F(2)+14,F(1)+16}=max{14,16)=16
F(6)= max{F(0)+30, F(3)+14,F(2)+16}=max {30,28,16}=30
F(7)=max {F(1)+30,F(4)+14,F(3)+16}=max{30,30,30}=30
F(8)=max{F(2)+30,F(5)+14,F(4)+16}=max{30, 30, 32}=32
F(9)=max{F(3)+30, F(6)+14, F(5)+16)=max{44,44,32}=44
F(10)=max{F(4)+30,F(7)+14,F(6)+16=max{44, 44, 46}=46
------------------------------------------------------------------------------------------------------------------------------
下面我们可以加上一个附加条件, 即是每个东西我们只可以用一次. 这样一来, 我们的函数就多了一个变量, j.
以前我们只需要把W变为w就可以了, 现在我们还需要把n变成j.
这回我们的选择题变为:
目前我们有总重量w-wj的和j-1个东西的情况下, 我们要不要加入商品j呢? 最后返回F(W,n)即可.
如果选择j,那么我们有,
F(w,j)=F(w-wj,j-1)+vj
如果不选择, 那么我们有,
F(w,j)=F(w,j-1)
所以,
F(w,j)=max{F(w-wj,j-1)+vj,F(w,j-1)}
那么基本状态又是什么呢?
很简单:如果没有东西, 是0. 如果没有袋子,也是0. 符号表示就是:
F(0,j)=0 and F(w,0)=0
这样一来我们就可以写伪代码了.
F(0,j)=0 and F(w,0)=0
for j=1 to n
for w = 1 to W
if wj>w F(w,j)=F(w,j-1)//如果东西 j 的重量大于这时的总重量, 肯定不要它
F(w,j)=max{F(w-wj,j-1)+vj,F(w,j-1)}
return F(W,n)
=========================================================================
那么如果我们再加一个条件, 也就是我们规定只能偷k个东西呢?
很简单, 这时我们不返回F(W,n),而是返回F(W,k) 就好了.
=========================================================================
那么如果我们换一个条件, 也就是说这时候我们每个东西都只可以偷m个,怎么办呢?
我们只要把从1到j变成从1到mj就可以了!最后我们返回 F(W,nm).
背包之01背包、完全背包、多重背包详解
— Tanky Woo(2010.07.31)
首先说下动态规划,动态规划这东西就和递归一样,只能找局部关系,若想全部列出来,是很难的,比如汉诺塔。你可以说先把除最后一层的其他所有层都移动到2,再把最后一层移动到3,最后再把其余的从2移动到3,这是一个直观的关系,但是想列举出来是很难的,也许当层数n=3时还可以模拟下,再大一些就不可能了,所以,诸如递归,动态规划之类的,不能细想,只能找局部关系。
图1.汉诺塔图片
(引至杭电课件:DP最关键的就是状态,在DP时用到的数组时,也就是存储的每个状态的最优值,也就是记忆化搜索)
要了解背包,首先得清楚动态规划:
动态规划算法可分解成从先到后的4个步骤:
1. 描述一个最优解的结构;2. 递归地定义最优解的值;3. 以“自底向上”的方式计算最优解的值;4. 从已计算的信息中构建出最优解的路径。
其中步骤1~3是动态规划求解问题的基础。如果题目只要求最优解的值,则步骤4可以省略。
背包的基本模型就是给你一个容量为V的背包 在一定的限制条件下放进最多(最少?)价值的东西
当前状态→ 以前状态
看了dd大牛的《背包九讲》,迷糊中带着一丝清醒,这里我也总结下01背包,完全背包,多重背包这三者的使用和区别,部分会引用dd大牛的《背包九讲》,如果有错,欢迎指出。 (www.wutianqi.com留言即可)
首先我们把三种情况放在一起来看:
01背包(ZeroOnePack): 有N件物品和一个容量为V的背包, 每种物品均只有一件。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。多重背包(MultiplePack): 有N种物品和一个容量为V的背包,第i种物品最多有n[i]件可用。每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
比较三个题目,会发现不同点在于每种背包的数量,01背包是每种只有一件,完全背包是每种无限件,而多重背包是每种有限件。
先来分析01背包:
01背包(ZeroOnePack): 有N件物品和一个容量为V的背包,每种物品均只有一件。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
把这个过程理解下:
在前i件物品放进容量v的背包时,
它有两种情况
情况一: 第i件不放进去,这时所得价值为:f[i-1][v]情况二: 第i件放进去,这时所得价值为:f[i-1][v-c[i]]+w[i]
(第二种是什么意思?就是如果第i件放进去,那么在容量v-c[i]里就要放进前i-1件物品)
最后比较第一种与第二种所得价值的大小,哪种相对大,f[i][v]的值就是哪种。 (这里是重点,理解!)
这里是用二维数组存储的,可以把空间优化,用一维数组存储。
用f[0..v]表示,f[v]表示把前i件物品放入容量为v的背包里得到的价值。把i从1~n(n件)循环后,最后f[v]表示所求最大值。
这里f[v]就相当于二维数组的f[i][v]。那么,如何得到f[i-1][v]和f[i-1][v-c[i]]+w[i]?(重点!思考)
首先要知道,我们是通过i从1到n的循环来依次表示前i件物品存入的状态。
即:for i=1..N
现在思考如何能在是f[v]表示当前状态是容量为v的背包所得价值,而又使f[v]和f[v-c[i]]+w[i]标签前一状态的价值?
逆序
这就是关键!
分析上面的代码:当内循环是逆序时,就可以保证后一个f[v]和f[v-c[i]]+w[i]是前一状态的!这里给大家一组测试数据: 测试数据: 10,3 3,4 4,5 5,6
图2: 01背包图(1)
这个图表画得很好,借此来分析:
C[v]从物品i=1开始,循环到物品3,期间,每次逆序得到容量v在前i件物品时可以得到的最大值。
(请在草稿纸上自己画一画)
这里以一道题目来具体看看:
分析:
图2: 01背包图(2)
具体根据上面的解释以及我给出的代码分析。这题很基础,看懂上面的知识应该就会做了。
完全背包:
完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。 完全背包按其思路仍然可以用一个二维数组来写出:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k*c[i]<=v}
同样可以转换成一维数组来表示:
伪代码如下:顺序
想必大家看出了和01背包的区别,这里的内循环是顺序的,而01背包是逆序的。
现在关键的是考虑:为何完全背包可以这么写?
在次我们先来回忆下,01背包逆序的原因?是为了是max中的两项是前一状态值,这就对了。 那么这里,我们顺序写,这里的max中的两项当然就是当前状态的值了,为何? 因为每种背包都是无限的。当我们把i从1到N循环时,f[v]表示容量为v在前i种背包时所得的价值,这里我们要添加的不是前一个背包,而是当前背包。所以我们要考虑的当然是当前状态。
这里同样给大家一道题目:
多重背包
多重背包(MultiplePack): 有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容量为v的背包的最大权值,则有状态转移方程:
f[i][v]=max{f[i-1][v-k*c[i]]+k*w[i]|0<=k<=n[i]}
这里同样转换为01背包:
普通的转换对于数量较多时,则可能会超时,可以转换成二进制(暂时不了解,所以先不讲) 对于普通的。就是多了一个中间的循环,把j=0~bag[i],表示把第i中背包从取0件枚举到取bag[i]件。
http://www.360doc.com/content/11/1218/16/3725126_173166153.shtml
http://steven-wang.appspot.com/dpa-knapsack-problem-31001.html
http://blog.csdn.net/hhygcy/article/details/3955683
No comments:
Post a Comment