【算法总结】-蛮力,贪心,动态规划 话谈背包问题
最近在和小伙伴们研究背包问题,对于背包问题的存在,相信已经算是一个经典问题了。本篇我们则从多方面来对背包问题有一个宏观的了解。
首先我们来简要描述一下何为背包问题?
经典问题
给定n个重量为W1,W2,W3,…,Wn,价值为V1,V2,V3…,Vn的物品和一个承重为W的背包,求这些物品中一个最有价值的子集,并且要能够装到背包中。
实际数值举例:
物品 | 重量 | 价值/美元 |
---|---|---|
1 | 2 | 12 |
2 | 1 | 10 |
3 | 3 | 20 |
4 | 2 | 15 |
背包所能承载的重量W=5
一、蛮力法
蛮力法是一种简单直接地解决问题的办法,常常直接基于问题的描述和所涉及的概念定义。
对于蛮力法解决背包问题,其实就是我们所谓的穷举列表法,把所有的可能情况都列举出来,从所有的情况中找到最大值。
如下表则是应用穷举法的所有可能情况:
子集 | 总重量 | 总价值/美元 |
---|---|---|
∅ | 0 | 0 |
{1} | 2 | 12 |
{2} | 1 | 10 |
{3} | 3 | 20 |
{4} | 2 | 15 |
{1,2} | 3 | 32 |
{1,3} | 5 | 32 |
{1,4} | 4 | 27 |
{2,3} | 4 | 30 |
{2,4} | 3 | 25 |
{3,4} | 5 | 35 |
{1,2,3} | 6 | 超重,不可行 |
{1,2,4} | 5 | 37 |
{1,3,4} | 7 | 超重,不可行 |
{2,3,4} | 6 | 超重,不可行 |
{1,2,3,4} | 8 | 超重,不可行 |
通过价值列判定,发现背包中放入1,2,4三个物品时,价值最高。当物品数量为4的时候,通过穷举法,我们需要2^4次列举,无疑使用穷举查找型算法对于任何输入都是低效率的。
二、贪婪技术
使用蛮力法自我感觉其实就是按规矩办事,我把所有的情况都列出来,你想要哪种选哪种;而贪婪技术,针对“贪婪”一词可想而知。
何为贪婪技术?贪婪技术所走的每一步必须满足如下的三个条件:
- 可行的:即它必须满足问题的约束。
- 局部最优:它是当前步骤中最优的选择。
- 不可取消:即一旦做出选择,则后续步骤中无法改变。
所以对于贪婪算法而言,我首先上来肯定选最有价值的,物品3价值最大,所以先选择物品3,价值为20,重量为3;这时候背包还剩余W-3=2,然后从剩余的里边再选择一个价值最大的,即物品4(该物品重量<=2,此实例中,物品4重量为2,价值最大,所以选择了物品4;假若有一个重量为1的物品,价值比物品4还大,那肯定就选择价值最大的了).所以此实例中,若采用贪婪算法,我们则选择3,4两个物品时,背包的价值最高。
三、动态规划
对于动态规划这种设计技术而言,其实它是存在一段很有趣的历史的,这里不便多言。那么何为动态规划呢?
wiki中说Dynamic programming是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。常常适用于有重叠子问题[1]和最优子结构性质的问题。
动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再根据子问题的解以得出原问题的解。
所以对于背包问题也是如此,我们首先得考虑如何将其分配成一个一个的小问题。
设F(i,j)为该实例的最优解的物品总价值,换句话理解,就是能够放入称重量为j(0<j<=W)的背包中的前i个物品中最有价值子集的总价值。
所以有两种情况:
- 不包括第i个物品的子集中,最优子集的价值为F(i-1,j)
- 在包括第i个物品的子集中(j-Wi>=0),那么最优子集则是该物品包括或者包括两者中选择最优子集。
注:横坐标代表承重梁j,0<=j<=5
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | |||||
2 | 0 | |||||
3 | 0 | |||||
4 | 0 |
- 当背包中不放任何物品时,价值为0。
- 【 j=1,i=1】j-W1=1-2<0;F(i,j)=F(0,1)=0
- 【j=2,i=1】j-W1=2-2=0;F(i,j)= max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,2),F(0,0)+v1}=12
- 【j=3,i=1】j-W1=3-2=1;F(i,j)= max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,3),F(0,1)+v1}=12
- 【j=4,i=1】j-W1=4-2=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,4),F(0,2)+v1}=12
- 【j=5,i=1】j-W1=5-2=3;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,5),F(0,3)+v1}=12
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | |||||
3 | 0 | |||||
4 | 0 |
- 【j=1,i=2】j-W2=1-1=0;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,1),F(1,0)+v2}=10
- 【j=2;i=2】j-W2=2-1=1;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,2),F(1,1)+v2}=12
- 【j=3,i=2】j-W2=3-1=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,3),F(1,2)+v2}=22
- 【j=4,i=2】j-W2=4-1=3;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,4),F(1,3)+v2}=22
- 【j=5,i=2】j-W2=5-1=4;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,5),F(1,4}+v2}=22
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | 10 | 12 | 22 | 22 | 22 |
3 | 0 | |||||
4 | 0 |
- 【j=1,i=3】j-W3=1-3<0;F(i,j)=F(2,1)=10
- 【j=2,i=3】j-W3=2-3<0;F(i,j)=F(2,2)=12
- 【j=3,i=3】j-W3=3-3=0;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(2,3),F(2,0)+v3}=22
- 【j=4,i=3】j-W3=4-3=1;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(2,4),F(2,1)+v3}=30
- 【j=5,i=3】j-W4=5-3=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(2,5},F(2,2)+v3}=32
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | 10 | 12 | 22 | 22 | 22 |
3 | 0 | 10 | 12 | 22 | 30 | 32 |
4 | 0 |
- 【j=1,i=4】j-W3=1-2<0;F(i,j)=F(3,1)=10
- 【j=2,i=4】j-W3=2-2=0;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(3,2},F(3,0)+v4}=15
- 【j=3,i=4】j-W3=3-2=1;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(3,3),F(3,1)+v4}=25
- 【j=4,i=4】j-W3=4-2=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(3,4),F(3,2)+v4}=30
- 【j=5,i=4】j-W4=5-2=3;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(3,5},F(3,3)+v4}=37
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | 10 | 12 | 22 | 22 | 22 |
3 | 0 | 10 | 12 | 22 | 30 | 32 |
4 | 0 | 10 | 15 | 25 | 30 | 37 |
通过如上表格,我们可以很清晰的发现,最大价值为F(4,5)=37,采用回溯法的过程,我们可以自底向上判定其物品。因为F(4,5)>F(3,5),物品4为最优解物品,剩余重量为5-2=3;因为F(3,3)=F(2,2),所以物品3并非最优解;因为F(2,3)>F(1,3),所以物品2为最优解物品,剩余重量为3-1=2;因为F(1,2)>F(0,2),所以物品1为最优解物品。所以采用动态规划问题解决背包问题,物品1,2,4放入背包,价值最大。
通过回溯法我们发现,因为F(3,3)和F(2,2)价值是一样的,所以在如上的计算过程中,有些子问题对于求解并非是必须的,所以我们完全可以利用动态规划的记忆法,再次优化背包问题。该方式则是通过自底向上的方式计算,这样可以保证需要哪个子问题求解哪个子问题,避免非必要的子问题出现。
采用的公式依旧是
初始状态:
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | |||||
2 | 0 | |||||
3 | 0 | |||||
4 | 0 |
求解 | 过程 | 需要的子集 |
---|---|---|
F(4,5) | 【j=5,i=4】j-W4=5-2=3;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(3,5},F(3,3)+v4} | F(3,5) F(3,3) |
F(3,5) | 【j=5,i=3】j-W4=5-3=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(2,5},F(2,2)+v3} | F(2,5) F(2,2) |
F(3,3) | 【j=3,i=3】j-W3=3-3=0;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(2,3),F(2,0)+v3} | F(2,3) F(2,0) |
F(2,5) | 【j=5,i=2】j-W2=5-1=4;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,5),F(1,4}+v2} | F(1,5) F(1,4) |
F(2,3) | 【j=3,i=2】j-W2=3-1=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,3),F(1,2)+v2} | F(1,3) F(1,2) |
F(2,2) | 【j=2;i=2】j-W2=2-1=1;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(1,2),F(1,1)+v2} | F(1,2) F(1,0) |
F(1,5) | 【j=5,i=1】j-W1=5-2=3;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,5),F(0,3)+v1} | F(0,5) F(0,3) |
F(1,4) | 【j=4,i=1】j-W1=4-2=2;F(i,j)=max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,4),F(0,2)+v1} | F(0,4) F(0,2) |
F(1,3) | 【j=3,i=1】j-W1=3-2=1;F(i,j)= max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,3),F(0,1)+v1} | F(0,3) F(0,1) |
F(1,2) | 【j=2,i=1】j-W1=2-2=0;F(i,j)= max{F(i-1,j),vi+F(i-1,j-Wi)}=max{F(0,2),F(0,0)+v1} | F(0,2) F(0,0) |
F(1,1) | 【 j=1,i=1】j-W1=1-2<0;F(i,j)=F(0,1)=0 | F(0,1) |
通过如上子集需要,所以我们最终只需要计算F(1,1),F(1,2),F(1,3),F(1,4),F(1,5),F(2,2),F(2,3),F(2,5),F(3,3),F(3,5),F(4,5)等子集即可。
最后的表格为:
i | 0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 12 | 12 | 12 | 12 |
2 | 0 | - | 12 | 22 | - | 22 |
3 | 0 | - | - | 22 | - | 32 |
4 | 0 | - | - | - | - | 37 |
通过如上过程,我们可以很清晰的发现,其实虽然去除了无谓的子问题计算,但是这种记忆式的过程其实就是一种递归的思想。那谈到递归,效率肯定不会高到哪里去。但是相比其他思想而言,动态规划的记忆法还是效率很高的,只是提高效率的维度不大,其时间复杂度和上述讲述的自底向上算法的层级是一样的。
通过以上三个思想的背包问题总结,其实我们可以总结如下规律:
背包可扩展/非0-1问题 | 0-1问题 | n很小 | |
---|---|---|---|
蛮力法 | ※ | ||
贪心 | ※ | ||
动态规划 | ※ | ※ |
为何这么说呢,因为通过蛮力法我们发现,当n=4的时候,我们就得罗列2^4次,所以这种穷举法的方式根本不适用于背包问题;因为贪心问题是局部最优进而整体最优,所以若背包可扩展或者是非0-1问题(可切割),那么我肯定毫无疑问,选择贪心算法了,因为贪婪每次选择的都是当前里边最优的一个选择;而所以的0-1背包问题,使用动态规划的记忆方式,去除多余子问题的计算,无疑是这三个思想里边最好的一种方式。