http://hawstein.com/posts/dp-knapsack.html
话说有一哥们去森林里玩发现了一堆宝石,他数了数,一共同拥有n个。 但他身上能装宝石的就仅仅有一个背包,背包的容量为C。这哥们把n个宝石排成一排并编上号: 0,1,2,…,n-1。
第i个宝石相应的体积和价值分别为V[i]和W[i] 。排好后这哥们開始思考: 背包总共也就仅仅能装下体积为C的东西。那我要装下哪些宝石才干让我获得最大的利益呢?
OK,假设是你,你会怎么做?你斩钉截铁的说:动态规划啊!恭喜你,答对了。
那么让我们来看看。动态规划中最最最重要的两个概念: 状态和状态转移方程在这个问题中各自是什么。
我们要如何去定义状态呢?这个状态总不能是凭空想象或是从天上掉下来的吧。 为了方便说明,让我们先实例化上面的问题。
一般遇到n,你就果断地给n赋予一个非常小的数, 比方n=3。然后设背包容量C=10,三个宝石的体积为5,4。3,相应的价值为20,10。12。
对于这个样例,我想智商大于0的人都知道正解应该是把体积为5和3的宝石装到背包里, 此时相应的价值是20+12=32。接下来,我们把第三个宝石拿走, 同一时候背包容量减去第三个宝石的体积(由于它是装入背包的宝石之中的一个)。 于是问题的各參数变为:n=2,C=7,体积{5,4}。价值{20,10}。好了。 如今这个问题的解是什么?我想智商等于0的也解得出了:把体积为5的宝石放入背包 (然后剩下体积2。装不下第二个宝石。仅仅能眼睁睁看着它溜走),此时价值为20。
这样一来,我们发现,n=3时。放入背包的是0号和2号宝石。当n=2时, 我们放入的是0号宝石。这并非一个偶然。没错。 这就是传说中的“全局最优解包括局部最优解”(n=2是n=3情况的一个局部子问题)。 绕了那么大的圈子。你可能要问。这都哪跟哪啊?说好的状态呢?说好的状态转移方程呢? 别急,它们已经呼之欲出了。
我们再把上面的样例理一下。当n=2时,我们要求的是前2个宝石。 装到体积为7的背包里能达到的最大价值;当n=3时,我们要求的是前3个宝石, 装到体积为10的背包里能达到的最大价值。
有没有发现它们事实上是一个句式。OK, 让我们形式化地表示一下它们, 定义d(i,j)为前i个宝石装到剩余体积为j的背包里能达到的最大价值。 那么上面两句话即为:d(2, 7)和d(3, 10)。这样看着真是爽多了, 而这两个看着非常爽的符号就是我们要找的状态了。 即状态d(i,j)表示前i个宝石装到剩余体积为j的背包里能达到的最大价值
。
上面那么多的文字,用一句话概括就是:依据子问题定义状态。你找到子问题。 状态也就浮出水面了。而我们终于要求解的最大价值即为d(n, C):前n个宝石 (0,1,2…,n-1)装入剩余容量为C的背包中的最大价值。状态好不easy找到了。 状态转移方程呢?顾名思义,状态转移方程就是描写叙述状态是怎么转移的方程(好废话!
)。 那么回到样例。d(2, 7)和d(3, 10)是怎么转移的?来,我们来说说2号宝石 (记住宝石编号是从0開始的)。从d(2, 7)到d(3, 10)就隔了这个2号宝石。 它有两种情况,装或者不装入背包。假设装入,在面对前2个宝石时,
背包就仅仅剩下体积7来装它们,而对应的要加上2号宝石的价值12, d(3, 10)=d(2, 10-3)+12=d(2, 7)+12。假设不装入,体积仍为10。价值自然不变了, d(3, 10)=d(2, 10)。记住。d(3, 10)表示的是前3个宝石装入到剩余体积为10 的背包里能达到的最大价值
。既然是最大价值,就有d(3,
10)=max{ d(2, 10), d(2, 7)+12 }。
好了,这条方程描写叙述了状态d(i, j)的一些关系。 没错,它就是状态转移方程了。把它形式化一下:d(i, j)=max{ d(i-1, j), d(i-1,j-V[i-1]) + W[i-1] }。注意讨论前i个宝石装入背包的时候, 事实上是在考查第i-1个宝石装不装入背包(由于宝石是从0開始编号的)。至此。 状态和状态转移方程都已经有了。
//volume[]宝石体积数组,worthness[]宝石价值数组,n宝石种类个数。C背包体积 //状态d(i,j)表示前i个宝石装到剩余体积为j的背包里能达到的最大价值 int Knapsack(int volume[],int worthness[],int n,int C) { int maxworth; int **d=new int*[n+1]; for (int i=0;i<=n;i++)//前n个相应第n-1下标元素 { d[i]=new int[C+1]; } for (int j=0;j<=C;j++) { for (int i=0;i<=n;i++) { if (i==0) { d[i][j]=0; } else { d[i][j]=d[i-1][j];//默认不取第i个宝石 } if (i>0&&j>=volume[i-1]) {//注意讨论前i个宝石装入背包的时候, 事实上是在考查第i-1个宝石装不装入背包(由于宝石是从0開始编号的)。 maxworth=d[i-1][j-volume[i-1]]+worthness[i-1];//取第i个宝石 if (maxworth>=d[i][j])//取价值最大的 { d[i][j]=maxworth; } } } } int *uesd=new int[n];//标记是否拾起 for (int i=0;i<n;i++) { uesd[i]=0;//初始时未拾起 } int j = C; for(int i=n; i>0; --i){ if(d[i][j] > d[i-1][j])//第i个拾起 { uesd[i-1] = 1; j = j - volume[i-1]; } } for(int i=0; i<n; ++i) printf("%d ", uesd[i]); return maxworth; }
版权声明:本文博客原创文章,博客,未经同意,不得转载。