动态规划-01背包问题解析

问题描述

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

i(物品编号) 1 2 3 4
w(体积) 2 3 4 5
v(价值) 3 4 5 6

思路概述

多步决策问题–>子问题定义–>递归表达式–>重复冗余子问题

01背包问题可以进行多步决策,每一次选择一个物品是否放入背包,0就是不放入,1就是放入,然后我们可以得到下面的递推表达式:
在这里插入图片描述
通过填写表把所有已经解决的子问题答案纪录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。

解决方法

动态规划1

  1. 把背包问题抽象化( X 1 , X 2 , … , X n X_1,X_2,…,X_n X1X2Xn,其中 X i X_i Xi取0或1,表示第 i 个物品选或不选), V i V_i Vi表示第 i 个物品的价值, W i W_i Wi表示第 i 个物品的体积(重量);
  2. 建立模型,求 m a x ( V 1 X 1 + V 2 X 2 + … + V n X n ) max(V_1X_1+V_2X_2+…+V_nX_n) max(V1X1+V2X2++VnXn)
  3. 约束条件, W 1 X 1 + W 2 X 2 + … + W n X n < c a p a c i t y W_1X_1+W_2X_2+…+W_nX_n<capacity W1X1+W2X2++WnXn<capacity
  4. 定义V(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值;
  5. 最优性原理是动态规划的基础,最优性原理是指“多阶段决策过程的最优决策序列具有这样的性质:不论初始状态和初始决策如何,对于前面决策所造成的某一状态而言,其后各阶段的决策序列必须构成最优策略”。
  6. 寻找递推关系式,面对当前商品有两种可能性:
  • 第一,包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
  • 第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }
  • 其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);
  • 由此可以得出递推关系式:
  • 1) j<w(i) V(i,j)=V(i-1,j)
  • 2) j>=w(i) V(i,j)=max{ V(i-1,j),V(i-1,j-w(i))+v(i) }
  1. 填表
    首先初始化边界条件,V(0,j)=V(i,0)=0;
    在这里插入图片描述
    然后一行一行的填表,
  • 如,i=1,j=1,w(1)=2,v(1)=3,有j<w(1),故V(1,1)=V(1-1,1)=0;
  • 如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;
  • 如此下去,填到最后一个,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;
  • 所以填完表如下图:
    在这里插入图片描述
  1. 代码
void Bag()
{
    
    
    int 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];
                }
            }
        }
    }
}
  1. 根据最优解回溯找出解的组成
    在这里插入图片描述
void FindAns(int i,int j)//寻找解的组成方式
{
    
    
    if(i>=0)
    {
    
    
        if(V[i][j]==V[i-1][j])//相等说明没装
        {
    
    
            item[i]=0;//全局变量,标记未被选中
            FindAns(i-1,j);
        }
        else if( j-w[i]>=0 && V[i][j]==V[i-1][j-w[i]]+v[i] )
        {
    
    
            item[i]=1;//标记已被选中
            FindAns(i-1,j-w[i]);//回到装包之前的位置
        }
    }
}

空间优化

空间优化,每一次V(i)(j)改变的值只与V(i-1)(x) {x:1…j}有关,V(i-1)(x)是前一次i循环保存下来的值;
因此,可以将V缩减成一维数组,从而达到优化空间的目的,状态转移方程转换为 B(j)= max{B(j), B(j-w(i))+v(i)};
并且,状态转移方程,每一次推导V(i)(j)是通过V(i-1)(j-w(i))来推导的,所以一维数组中j的扫描顺序应该从大到小(capacity到0),否者前一次循环保存下来的值将会被修改,从而造成错误。
当i=3时,有下图计算
在这里插入图片描述
如果j不逆序而采用正序j=0…capacity,如上图所示,当j=8时应该有B(8)=B(8-w(3))+v(3)=B(4)+5,然而此时的B(4)已经在j=4的时候被修改过了,原来的B(4)=4,现在B(4)=5,所以计算得出B(8)=5+5=10,显然这于正确答案不符合;所以该一维数组后面的值需要前面的值进行运算再改动,如果正序便利,则前面的值将有可能被修改掉从而造成后面数据的错误;相反如果逆序遍历,先修改后面的数据再修改前面的数据,此种情况就不会出错了;

void BagBetter()//优化空间后的动态规划
{
    
    
    int i,j;
    for(i=1;i<=number;i++)
    {
    
    
        for(j=capacity;j>=0;j--)
        {
    
    
            if(B[j]<=B[j-w[i]]+v[i] && j-w[i]>=0 )//二维变一维
            {
    
    
                B[j]=B[j-w[i]]+v[i];
            }
        }
    }
}

然而不足的是,虽然优化了动态规划的空间,但是该方法不能找到最优解的解组成,因为动态规划寻早解组成一定得在确定了最优解的前提下再往回找解的构成,而优化后的动态规划只用了一维数组,之前的数据已经被覆盖掉,所以没办法寻找,所以两种方法各有其优点。

小结

动态规划待解决的原问题较难,但此问题可以被不断拆分成一个个小问题,而小问题的解是非常容易获得的;如果单单只是利用递归的方法来解决原问题,那么采用的是分治法的思想,动态规划具有记忆性,将子问题的解都记录下来,以免在递归的过程中重复计算,从而减少了计算量。

猜你喜欢

转载自blog.csdn.net/qq_32505207/article/details/107945479
今日推荐