c语言dp算法解决背包问题,DP求解完全背包问题及其优化原理

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

完全背包问题是一类经典的DP(Dynamic Programming,动态规划)问题,问题描述如下:

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

假设第i种物品选择了ki个,ki∈N,则

目标函数为:

max{Σkivi}

约束条件为:

$ \sum k_i w_i \leq W $

2. 完全背包问题的平凡解法

首先对所有物品种类进行1~n的编号。

设函数F(i,j),其意义为:从编号1~i的种类中选取物品,装入最大允许重量为j的背包中,所能获得的最大总价值是F(i,j)。

首先考虑基准情况:选0个物品,即i=0,这时:

F(i,j) = F(0,j) = 0

对于子问题F(i,j)的求解,其最优解的决策依赖于涉及1~(i-1)类物品的子问题F(i-1,j)。

当前要做出决策,需选k个i类物品(k可以为0),根据物品的重量分为如下两种情况:

1。若物品重量wi不超过背包的允许重量j,则可分别选0个、...、k个该物品,kwi都不超过允许重量j。最后再从所有选择结果中挑选最大总价值,作为当前最优解。

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

2。若物品重量wi超过背包的允许重量j,则无法放入当前物品:

F(i,j) = F(i-1,j)

上述两个递推式揭示了当前状态与上一状态之间的转移关系。加上基准情况,我们发现算法已经封闭了,可以编程实现。

核心程序

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

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

初始化:清零dp数组

for(int i=1;i<=n;++i) {for(int j=0;j<=t;++j) {if(j

dp[i][j]=dp[i-1][j];

}else{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*v[i]));

}

}

}

}

可以看出渐近复杂度O(n×t×w),为三次复杂度,因此该程序能解决的问题规模非常有限。但正如标题所说的,平凡解法还有较大可优化的空间。

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

利用循环间数据相关性优化算法是一种常见的技巧。例如“最大子段和”问题的蛮力解法中,利用数据相关性可以将O(n^3)的复杂度直接优化为O(n^2)。同理,在求Σnx=1 x!(连续阶乘和)问题中,利用数据相关性可将O(n^2)直接优化为O(n)。这些例子展现了该技巧的生命力。

回到我们的DP解法,观察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循环除了第一趟执行对结果有贡献外,后续执行只是在重复之前已经完成的计算,有希望将k循环合并到j循环中。

为了验证这种想法,推导如下:

题设:

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

考虑当k==0时,F(i-1,j - k × wi) + k × vi退化为F(i-1,j),所以

F(i,j) = max(F(i-1,j), max{F(i-1,j - k × wi) + k × vi|(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) × vi|(k>=0)})

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

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

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

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

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

这便是最终的表达式,可以看到成功消去了k。

根据推导的状态转移方程编写程序,时间复杂度直接从三次降至了二次。

需要注意,因为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

dp[i][j]=dp[i-1][j];

}else{

dp[i][j]=max(dp[i-1][j], dp[i][j-w[i]]+v[i]);

}

}

}

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

优化前,dp数组的空间复杂度是O(n×t)的。

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

仍然有两种情况:

1。对于j

2。对于其它非1。的情况,需要更新dp行,dp[j]=max(dp[j], dp[j-w[i]]+v[i]); 加粗部分反映了“原地”更新策略。

for(int i=1;i<=n;++i) {for(int j=w[i];j<=t;++j) {

dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

}

}

因为情况1。的存在,j的取值必须以w[i]为区间起点。

值得注意的是,j循环按递增顺序枚举,这与我们在第3节中得出的结论一致。

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

5. 结论

首先描述了完全背包问题,然后分析了最直观的trivial解法,由平凡解法发现循环间的数据相关性,推导出优化后的状态转移方程,推导结论使算法时间复杂度降至O(n×t)。接下来从另一角度——空间复杂度分析dp数组降维优化,使空间复杂度降低至O(n)。综合时空两个方面,得出了DP求解此类问题的优化方法。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值