动态规划——01背包问题(一维数组、二维数组、初始化)

一、问题描述

有n 个物品,它们有各自的重量和价值,现有给定容量的背包,
如何让背包里装入的物品具有最大的价值总和?

二、动态规划原理

动态规划是将大问题分解成许多小问题,通过寻找大问题与小问
题之间的递推关系,解决一个一个小问题,最终达到解决原问题
的目的。通过填表将每个子问题的解记录下来,在新问题里需要
用到时可以直接提取,节约时间。

例题

四个物体,背包容量为8.

i 1 2 3 4
w(体积) 2 3 4 5
v(价值) 3 4 5 6

解题过程:
1、创建一个新的变量a,a的值可以为1或0;
2、建立关系模型,max(v1a+v2a+v3a+v4a),a的值可以为1或0;
3、v1w1a+v2w2a+v3w3a+v4w4a<=背包容量,a的值可以为1或0;
4、本题有两个变量i(物品的个数),j(背包的容量);(i,j都是变化的)
5、那么定义V(i,j)为当前背包容量j,前i个物品最佳组合对应的价值
6、寻找递推关系,一件商品存在两种可能性:
第一,当前包的容量比该商品体积小,那么此时无法取到,即V(i,j)=V(i-1,j);
第二,当前包的容量比该商品大,但是取还是不取,当然是选择价值大的啦。
V(i,j)=max(V(i-1,j),V(i-1,j-w(i))+V(i));
7、创建二维列表依次遍历每一个物品就好了。

i/j 0 1 2 3 4 5 6 7 8
0 0 0 0 0 0 0 0 0 0
1 0 0 3 3 3 3 3 3 3
2 0 0 3 4 4 7 7 7 7
3 0 0 3 4 5 7 8 9 9
4 0 0 3 4 5 7 8 9 10

分析

(1) 如,i=1,j=1,w(1)=2,v(1)=3,有j (2) 又如i=1,j=2,w(1)=2,v(1)=3,有j=w(1),
故V(1,2)=max{ V(1-1,2),V(1-1,2-w(1))+v(1) }=max{0,0+3}=3;
(3) 如此下去,填到最后一个,i=4,j=8,w(4)=5,v(4)=6,有j>w(4),
故V(4,8)=max{ V(4-1,8),V(4-1,8-w(4))+v(4) }=max{9,4+6}=10;

背包代码–该代码以自定义函数的形式写的

 void Find_Max()//动态规划
  {
      int i,j;//i为物品,j为容量
      for(i=1;i<=number;i++)
      {
          for(j=1;j<=capacity;j++)
          {
              if(j<w[i])//包装不进
             {
                 V[i][j]=V[i-1][j];
             }
             else//能装
             {
                 if(V[i-1][j]>V[i-1][j-w[i]]+v[i])//不装价值大
                 {
                     V[i][j]=V[i-1][j];
                 }
                 else//前i-1个物品的最优解与第i个物品的价值之和更大
                 {
                     V[i][j]=V[i-1][j-w[i]]+v[i];
                 }
             }
         }
     }
 }

重新理解01背包问题

这是对01背包问题的重新理解,之前理解的不是很到位,总感觉少点什么。
这是最基本的背包问题,特点:每种物品只有一件,可以选择放还是不放。
用子问题定义状态:即F[i,v]表示前i件物品放入一个容量为v的背包可以获得的最大价值。

F[i,v]=max (F[i-1,v],F[i-1,v-ci]+Wi)
这个方程非常重要,基本上所有背包问题相关的问题都是由它衍生出来的

这里有必要详细解释一下
将前i件物品放入容量为v的背包中,这个子问题,若只考虑第i件物品的策略(放还是不放),
那么可以转换为一个只和前i-1件物品相关的问题。如果不放第i件物品,那么问题就转换为
“前i-1件物品放入容量为v的背包中”,价值为F[i-1,v];如果放入第i件物品,那么问题就
转换为“前i-1件物品放入剩下的容量为v-ci的背包中”,此时能获得最大价值就是F[i-1,v-ci]
在加上第i件的物品的获得的价值Wi。

优化空间复杂度

我们可知时间复杂度为O(VN),这不可改变,那么空间复杂度呢,上面代码用的二维数组所以空间复杂度O(VN),
那么我们是否可以优化空间复杂度,如果可以只能是一维数组了。
如何只用一个数组?看上面代码是从容量为0开始遍历,那反向遍历这样就可以用以为数组代替。

核心思想:
for i:=1 to N do
for j=V downto c[i] do
if f[j-c[i]]+w[i]>f[j] then
f[j]=f[j-c[i]]+w[i];

代码

void Find_max2()
{
	int i,j;//i为物品,j为容量
	for(i=1;i<=number;++i)//number为个数 
	{
		for(j=1;j<=capacity;++j)//capacity为背包体积 
		{
			if(v[j-c[i]]+w[i]>v[j]) 
			{
				v[j]=v[j-c[i]]+w[i];
			}
		}
	 } 
}

初始化细节

在最优解的问题中,事实上有两种不太相同的问法。

1.恰好装满背包时的最优解

初始化时将f[0]为0,其余f[1…N]设为负无穷,这样可以保证最终解是恰好装满背包的。

2.只求价值最大

初始化将f[0…N]都设为0

原因:初始化数组F数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的nothing“恰好装满”,其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是-∞了。如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时状态的值也就全部为0了。

你可能感兴趣的:(动态规划)