《算法导论》读书笔记之第16章 0-1背包问题—动态规划求解
1、前言
前段时间忙着搞毕业论文,看书效率不高,导致博客一个多月没有更新了。前段时间真是有些堕落啊,混日子的感觉,很少不爽。今天开始继续看算法导论。今天继续学习动态规划和贪心算法。首先简单的介绍一下动态规划与贪心算法的各自特点及其区别。然后针对0-1背包问题进行讨论。最后给出一个简单的测试例子,联系动态规划实现0-1背包问题。
2、动态规划与贪心算法
关于动态规划的总结请参考http://www.cnblogs.com/Anker/archive/2013/03/15/2961725.html。这里重点介绍一下贪心算法的过程。贪心算法是通过一系列的选择来给出某一个问题的最优解,每次选择一个当前(看起来是)最佳的选择。贪心算法解决问题的步骤为:
(1)决定问题的最优子结构
(2)设计出一个递归解
(3)证明在递归的任一阶段,最优选择之一总是贪心选择。保证贪心选择总是安全的。
(4)证明通过贪心选择,所有子问题(除一个意外)都为空。
(5)设计出一个实现贪心策略的递归算法。
(6)将递归算法转换成迭代算法。
什么时候才能使用贪心算法的呢?书中给出了贪心算法的两个性质,只有最优化问题满足这些性质,就可采用贪心算法解决问题。
(1)贪心选择性质:一个全局最优解可以通过举办最优解(贪心)选择来达到。即:当考虑做选择时,只考虑对当前问题最佳的选择而不考虑子问题的结果。而在动态规划中,每一步都要做出选择,这些选择依赖于子问题的解。动态规划一般是自底向上,从小问题到大问题。贪心算法通常是自上而下,一个一个地做贪心选择,不断地将给定的问题实例规约为更小的子问题。
(2)最优子结构:问题的一个最优解包含了其子问题的最优解。
动态规划与贪心的区别:
贪心算法:
(1)贪心算法中,作出的每步贪心决策都无法改变,因为贪心策略是由上一步的最优解推导下一步的最优解,而上一部之前的最优解则不作保留;
(2)由(1)中的介绍,可以知道贪心法正确的条件是:每一步的最优解一定包含上一步的最优解。
动态规划算法:
(1)全局最优解中一定包含某个局部最优解,但不一定包含前一个局部最优解,因此需要记录之前的所有最优解 ;
(2)动态规划的关键是状态转移方程,即如何由以求出的局部最优解来推导全局最优解 ;
(3)边界条件:即最简单的,可以直接得出的局部最优解。
3、0-1背包问题描述
有一个窃贼在偷窃一家商店时发现有n件物品,第i件物品价值为vi元,重量为wi,假设vi和wi都为整数。他希望带走的东西越值钱越好,但他的背包中之多只能装下W磅的东西,W为一整数。他应该带走哪几样东西?
0-1背包问题中:每件物品或被带走,或被留下,(需要做出0-1选择)。小偷不能只带走某个物品的一部分或带走两次以上同一个物品。
部分背包问题:小偷可以只带走某个物品的一部分,不必做出0-1选择。
4、0-1背包问题解决方法
0-1背包问题是个典型举办子结构的问题,但是只能采用动态规划来解决,而不能采用贪心算法。因为在0-1背包问题中,在选择是否要把一个物品加到背包中,必须把该物品加进去的子问题的解与不取该物品的子问题的解进行比较。这种方式形成的问题导致了许多重叠子问题,满足动态规划的特征。动态规划解决0-1背包问题步骤如下:
0-1背包问题子结构:选择一个给定物品i,则需要比较选择i的形成的子问题的最优解与不选择i的子问题的最优解。分成两个子问题,进行选择比较,选择最优的。
0-1背包问题递归过程:设有n个物品,背包的重量为w,C[i][w]为最优解。即:
课后习题给出了伪代码:
5、编程实现
现在给定3个物品,背包的容量为50磅。物品1重10磅,价值为60,物品2重20磅,价值为100,物品3重30磅,价值为120。采用动态规划可以知道最优解为220,选择物品2和3。采用C++语言实现如下:
1 #include <iostream> 2 using namespace std; 3 4 //物品数据结构 5 typedef struct commodity 6 { 7 int value; //价值 8 int weight; //重量 9 }commodity; 10 11 const int N = 3; //物品个数 12 const int W = 50; //背包的容量 13 14 //初始物品信息 15 commodity goods[N+1]={{0,0},{60,10},{100,20},{120,30}}; 16 int select[N+1][W+1]; 17 18 int max_value(); 19 20 int main() 21 { 22 int maxvalue = max_value(); 23 cout<<"The max value is: "; 24 cout<<maxvalue<<endl; 25 int remainspace = W; 26 //输出所选择的物品列表: 27 for(int i=N; i>=1; i--) 28 { 29 if (remainspace >= goods[i].weight) 30 { 31 if ((select[i][remainspace]-select[i-1][remainspace-goods[i].weight]==goods[i].value)) 32 { 33 cout << "item " << i << " is selected!" << endl; 34 remainspace = remainspace - goods[i].weight;//如果第i个物品被选择,那么背包剩余容量将减去第i个物品的重量 ; 35 } 36 } 37 } 38 return 0; 39 } 40 int max_value() 41 { 42 //初始没有物品时候,背包的价值为0 43 for(int w=1;w<=W;++w) 44 select[0][w] = 0; 45 for(int i=1;i<=N;++i) 46 { 47 select[i][0] = 0; //背包容量为0时,最大价值为0 48 for(int w=1;w<=W;++w) 49 { 50 if(goods[i].weight <= w) //当前物品i的重量小于等于w,进行选择 51 { 52 if( (goods[i].value + select[i-1][w-goods[i].weight]) > select[i-1][w]) 53 select[i][w] = goods[i].value + select[i-1][w-goods[i].weight]; 54 else 55 select[i][w] = select[i-1][w]; 56 } 57 else //当前物品i的重量大于w,不选择 58 select[i][w] = select[i-1][w]; 59 } 60 } 61 return select[N][W]; //最终求得最大值 62 }
程序测试结果如下: