博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

0-1背包和完全背包

Posted on 2011-05-14 14:09  各种不会  阅读(723)  评论(0)    收藏  举报

  一直以来对动态规划这个词都是怀着一种敬畏的心情去看待的,首先是刚开始接触的时候不是很理解这到底是神马东西,现在终于知道是神马了,但是又发现自己根本不会用这种方法,不知道有没有什么好的办法。我觉得吧,这东西还要多练习能用动态规划解决的经典问题,从中得到一些所谓只能意会不能言传的感性认识,这样才能在碰到新问题的时候用动态规划的思想去试着解决。下面首先还是复习下动态规划的基本概念吧,有人可能觉得这些概念根本没有用,但是我想说的是如果连这些基本概念都不懂的话,我认为你是不可能学会动态规划的。

  动态规划适用于求解“最优化问题”,也就是类似最大XX,最小XX的问题。动态规划的基本思想是将待求解的问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适用于用动态规划法求解的问题,经分解得到的子问题往往不是相互独立的。其实能够用动态规划解决的问题用递归的方法肯定也能够解决,但是如果使用递归来解决可能会重复计算某些子问题的结,因为递归的方法是自顶向下求解的。但是动态规划是自底向上的求解问题,将得到的子问题的解存放在一个数据结构中,在计算规模更大的子问题的时候如果需要用到子问题的解,可以直接通过查表得到,不需要重复计算。

  一般解动态规划问题的步骤:

  1. 找到最优解的性质并刻画其结构特征
  2. 递归的定义最优值
  3. 自底向上的方式计算最优值
  4. 根据计算最优值时得到的信息,构造最优解

动态规划的基本要素:

  1. 最优子结构:一个问题的最优解包含了其子问题的最优解,这种性质就叫最优子结构。在分析一个问题是否具有最优子结构时可以用到的方法:首先假设从问题的最优解得到的子问题的解不是最优的,然后想办法说明在这个假设下,可以构造出比原问题最优解更优的解,从而导致矛盾。利用问题的最优子结构性质,可以自底向上的由子问题的最优解得到问题的最优解。具有最优子结构是一个问题能使用动态规划方法求解的前提。一个问题可能具有多个最优子结构,选择合适的最优子结构,是能不能降低算法复杂度的关键。
  2. 重叠子问题:使用递归算法求解问题时,不是每次产生的子问题都是新问题,有的子问题会被重复多次的计算,这种性质就是子问题的重叠性质。

写了这么多的概念,下面来求解动态规划的经典问题:背包问题。

  • 0-1背包

首先分析0-1背包问题的最有子结构:定义f[i][v]表示前 i 种物品放入容量为v的背包中可以得到的最大价值。对于第 i 件物品来说,有两种选择,(1)不放入背包(2)放入背包,对于前 i 件物品可以获得的最大价值为两种情况的最大值,即:f[i][v] = MAX{f[i-1][v], f[i-1][v-v[i]]+w[i]},可以看到01背包有很明显的最有子结构。只要有了边界条件,就可以很容易的自底向上的计算问题的最优解即f[n][c](n为物品的总数,c为背包的容量)。f[0][1...c]=0, f[0...n][0]=0. 当你注意到计算f[i][1...c]只使用了f[i-1][1...c]有关的值的时候,可以想象,只用一个一维数组就可以得到最终的结果。具体的代码见文章末尾。

  • 完全背包问题

完全背包问题可以转化为0-1背包问题来求解,但是复杂度很高,还可以用二进制分解,这些都是在背包九讲中看的,这里只介绍最简单也是最有效的一种方法。对于完全背包问题,对于物品 i 来说可以有很多种选择,因为理论上可以放入无限多件,但是对于第i 种物品来说,也是只有两种选择:(1)不放入背包(2)放入背包。对于(1)很明显可以表示为f[i-1][v],对于(2)第i 种物品放入背包,放多少件不知道,但是至少有一件,对于这一件物品i 首先放入背包,所以背包容量变为v-v[i],这时候又转化为一个子问题,前i件物品放入容量为v-v[i]的背包中能获得的最大价值。所以状态转移方程为:f[i][v] = MAX{f[i-1][v], f[i][v-v[i]]+w[i]}.

具体的实现代码:

背包问题
1 // 01背包.cpp : Defines the entry point for the console application.
2  //
3
4 #include "stdafx.h"
5 #include <stdio.h>
6 #include <string.h>
7 #define MAX(x,y) x>y?x:y
8
9 // void Print(int (*data)[201])
10 // {
11 // for (int i=0; i<=8; i++)
12 // {
13 // for (int j=0; j<=200; j++)
14 // {
15 // printf("%d ", data[i][j]);
16 // }
17 // printf("\n");
18 // }
19 // }
20
21 //0-1背包的状态转移方程:f[i][v] = max{f[i-1][v], f[i-1][v-v[i]]+w[i]}
22 //其中f[i][v]表示前i件物品,放入容量为v的背包中可获得的最大价值
23 //最有子结构:对于物品i,有两个选择,1.放进背包 2.不放进背包
24 //放进背包的价值是f[i-1][v-v[i]]+w[i] 不放进背包的价值是f[i-1][v]
25 //求上面两个的最大值就可以得到最优的f[i][v]
26 int BeiBao(int w[], int c[], int bagNum, int v)
27 {
28 int f[10][201];
29 for (int i=0; i<=bagNum; i++)
30 {
31 f[i][0] = 0;
32 }
33 for (i=0; i<=v; i++)
34 {
35 f[0][i] = 0;
36 }
37
38 for (i=1; i<=bagNum; i++)
39 {
40 for (int j=1; j<=v; j++)
41 {
42 //f[i][v] = MAX(f[i-1][v], f[i-1][v-c[i]]+w[i]);
43 int temp1 = f[i-1][j];
44 int temp2 = 0;
45 if (j < c[i])
46 {
47 temp2 = 0;
48 }else{
49 temp2 = f[i-1][j-c[i]]+w[i];
50 }
51 f[i][j] = MAX(temp1, temp2);
52 }
53 }
54 //Print(f);
55 return f[bagNum][v];
56 }
57
58 int BeiBao1(int w[], int c[], int bagNum, int v)
59 {
60 int f[20];
61 for (int i=0;i<=v;i++)
62 {
63 f[i] = 0;
64 }
65 for (i=1;i<=bagNum;i++)
66 {
67 for (int j=v;j>=c[i];j--)
68 {
69 f[j] = MAX(f[j],f[j-c[i]]+w[i]);
70 }
71 for (int m=0; m<=v; m++)
72 {
73 printf("%d ", f[m]);
74 }
75 printf("\n");
76 }
77 return f[v];
78 }
79
80 //完全背包问题:f[i][v] = max{f[i-1][v], f[i][v-v[i]]+w[i]}
81 //对于容量为V的背包,对物品i有两个选择:1.不放物品i 这时候的最大价值就转化为前i-1个物品放入v背包的问题,即f[i-1][v]
82 //2.放入物品i 但是这次放物品i不是向上一个状态中放,而是要向当前状态放。所以应该是f[i][v-v[i]]+w[i] 也就是说在当前这件物品i
83 //放入之前可能已经放入了多件的物品i。
84 //当前这件物品i放进去之后,背包容量变成了v-v[i],这时候问题已经成为一个子问题:求解前i件物品放入容量为v-v[i]的
85 //背包中可获得的最优值问题,也就是f[i][v-v[i]]. f[i][v-v[i]]+w[i]也就是放入物品i能得到的最大值;
86 int CompletePack(int w[], int c[], int bagNum, int v)
87 {
88 int f[11];//v+1个
89 memset(f,0,sizeof(f));//将f[i]清空表示当有0件物品的时候可获得的最大价值为0
90 for (int i=1; i<=bagNum; i++)
91 {
92 for (int j=c[i]; j<=v; j++)
93 {
94 f[j] = MAX(f[j], f[j-c[i]]+w[i]);
95 }
96 }
97 return f[v];
98 }
99
100 int main(int argc, char* argv[])
101 {
102 //int w[]={0,83,14,54,79,72,52,48,62};
103 //int c[] = {0,79,58,86,11,28,62,15,68};
104 int w[] = {0,4,5,6};
105 int c[] = {0,3,4,5};
106 //printf("%d\n", BeiBao1(w,c,3,10));
107 printf("%d\n", CompletePack(w,c,3,10));
108 return 0;
109 }