• DP求解完全背包问题及其优化原理


    1. 完全背包问题的形式化描述

      有n种重量和价值分别为wipi的物品,$i=1,2,...,n$,从这些物品中挑选出总重量不超过W的物品,求所有挑选方案中价值总和的最大值。值得注意的是,每种物品都可以选取无限次。

      形式化描述为:设第i种物品选择了ki个,ki∈N,则问题即为求$  k_1,k_2,...,k_n $,满足:

      目标函数:

    egin{equation*} max{sum_{i=1}^{n}k_i p_i} end{equation*}

      约束条件:

    egin{equation*} left{ egin{aligned} sum_{i=1}^{n} k_i w_i leq W  \ k_i in N end{aligned}   ight. end{equation*}

      可以证明完全背包问题具有最优子结构性质,所以可以采用DP(Dynamic Programming)求解。

     

    2. 完全背包问题的trivial解法

      Trivial解法较为直观,因此首先介绍。但这种解法效率很低,后文将给出优化过程。

      

    设函数F(i,j)表示在重量限制j的条件下将前i种物品选择性装入背包获得的最大总价值。

      首先考虑基准(边界条件):选0个物品,即i=0,这时显然有

    F(0,j) = 0

     

      对于子问题F(i,j),其最优解依赖于前面子问题的最优解:

      在物品重量kwi不超过背包的重量限制j的前提下,分别选0,1,2,...,k个该物品。最后再从所有选择结果中挑选最大总价值,作为当前最优解,则状态转移方程为:

    F(i,j) = max{ F(i-1,j - k × wi) + k × pi      | (k>=0,kwi<=j) 

      这是因为,对于第i种物品,当装入k件物品wi后,背包的价值增加了k × pi,但剩余重量减少了k × wi。注意这只是第i种物品,剩下的其它物品要在前i-1种中选取,因此问题规约为在重量限制j - k × wi的条件下,将前i-1种物品选择性地装入背包所获得的最大总价值,即F(i-1,j - k × wi),可见这是一个与原问题性质完全相同的子问题。

      加之基准情况,算法过程已经封闭,其实现如下

    核心程序

    输入:物品种类数n,背包最大允许重量t,第i种物品重量w[i]、价值p[i]。

    输出:最大价值dp[n][t]。

    初始化:清零dp数组

    for(int i=1;i<=n;++i) {
        for(int j=0;j<=t;++j) {
            for(int k=0;k*w[i]<=j;++k) {
                dp[i][j]=max(dp[i][j],  max(dp[i-1][j], dp[i-1][j-k*w[i]]+k*p[i]));
            }
        }
    }

      分析可知,在最坏情况下,时间复杂度$ O(nt^2) $。但正如标题所说的,trivial解法还有可优化的空间。

      

    3. 利用数据相关性——时间复杂度优化

      观察for(j)与for(k)循环,两个循环间存在数据相关性(出现了重复计算),例如:F(i-1,j)中选择k个物品的情况,与F(i-1,j-wi)中选择k-1个情况完全相同,F(i-1,j)中k>=1的部分在计算F(i-1,j-wi)时就已经求出了。

      是否可以利用数据相关性消去k变量的循环?为了验证这种想法,推导如下:

    假设

      F(i,j) = max{F(i-1,j - k × wi) + k × p|(k>=0)}  ……①

    考虑当k==0时,F(i-1,j - k × wi) + k × pi等价于F(i-1,j),所以

      F(i,j) = max(F(i-1,j), max{F(i-1,j - k × wi) + k × pi   |(k>=1)} )

    将k代换为k+1。问题等价变形到k>=0的情况:

      F(i,j) = max(F(i-1,j), max{F(i-1,j - (k+1) × wi) + (k+1) × pi   |(k>=0)})

         = max(F(i-1,j), max{F(i-1,(j - wi) - k × wi) + k × pi pi  |(k>=0)} )    ……②

    结论仍不明显,将上式②下划线部分提到max{}后面,发现:

      F(i,j) = max(F(i-1,j), max{F(i-1,(j - wi) - k × wi) + k × pi  |(k>=0)} vi ) 

    对比①知,上式蓝色部分恰等价于F(i, j - wi),这基本验证了我们的想法。

      F(i,j) = max(F(i-1,j), F(i, j - wi) + pi

    这便是最终的状态转移方程,可以看到成功消去了k。最终的方程也非常直观: 对于第i种物品,当装入1件物品wi后,背包的价值增加了pi,但剩余重量减少了wi。注意剩下的其它物品仍然要在前i种中选取,即求解F(i, j - wi)。请对比F(i, j - wi)和trivial解法中的F(i-1, j - k × wi)以帮助理解。

      在该算法下,时间复杂度降至O(n×t)。

      值得注意的是,因为F(i-1,j)依赖于F(i-1,j-wi)的计算结果,j应递增枚举。

    for(int i=1;i<=n;++i) {
        for(int j=0;j<=t;++j) {
            if(j<w[i]) {
                dp[i][j]=dp[i-1][j];
            } else {
                dp[i][j]=max(dp[i-1][j], dp[i][j-w[i]]+p[i]);
            }
        }
    }

    4. 降低dp数组维度——空间复杂度优化

      在优化的解法中,空间复杂度为O(n×t)。

      注意到dp[i]一行的各元素值只依赖于前一行dp[i-1],而不依赖于dp[i-2]、……、dp[0]行。这启发我们只存储一行dp数组,然后在该行原地更新。

    for(int i=1;i<=n;++i) {
        for(int j=w[i];j<=t;++j) {
            dp[j]=max(dp[j],dp[j-w[i]]+p[i]);
        }
    }

      经过优化,空间复杂度降至了O(n)。

    5. 结论

      首先描述了完全背包问题,然后分析了最直观的trivial解法,由trivial解法发现循环间的数据相关性,推导出优化后的状态转移方程,推导结论使算法时间复杂度降至O(n×t)。

    接下来从另一角度——空间复杂度优化,使空间复杂度降低至O(n)。综合时空两个方面,得出了优化方法。

  • 相关阅读:
    任务框架--Quartz 配置文件
    地址和值
    线性基学习笔记
    S07
    如何在实际项目中使用PageHelper分页插件
    设计模式:原型模式
    常用JS代码片段
    Thomson Plaza里面的三家店以及水果大会
    13.搜索过滤
    07-多线程
  • 原文地址:https://www.cnblogs.com/cassuto/p/11907478.html
Copyright © 2020-2023  润新知