动态规划-背包问题
动态规划-背包问题
包含:
- 01背包求解及优化方法--跳跃点.
- 多重与完全背包的二进制优化和优先队列优化.
1.动态规划算法设计与实现
1.1.注意
• 问题的状态表示对能否用动态规划进行求解是至关重要的, 不恰当的状态表示将使问题的描述不具有最优子结构性质, 从而无法建立最优值的递归关系,动态规划的应用也就无从谈起。因此,状态表示和最优子结构性质的分析,是最关键的一步。
• 在算法的程序设计中,应充分利用子问题重叠性质来提高效率。更具体地说,应采用递推(迭代)的方法来编程计算由递归式定义的最优值,而不采用直接递归的方法。
1.2.步骤
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段,划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。如果确定了决策,状态 转移方程也就可写出。但事实上常常是反过来做,根据相邻两段各状态之间的关系来确定决策。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。
(5)程序设计实现:动态规划的主要难点在于理论上的设计,一旦设计完成,实现部分就会非常简单。
1.3.框架
(1)初始化(边界条件)
(2)\(for\quad i=2\quad to\quad n (顺推法) 或 for\quad i=n-1\quad to\quad 1(逆推法)\) 对i阶段的每一个决策点求局部最优
(3)确定和输出结束状态的值.
2. 0-1背包
2.1.描述
有\(N\)件物品和一个容量为\(C\)的背包。第\(i\)件物品的费用是\(w[i]\), 价值是\(v[i]\),求将哪些物品装入背包可使价值总和最大。
如果在选择装入背包的物品时,对每种物品i只有两种选择: 装入背包或不装入背包,则称为0-1背包问题。
2.2.算法描述
设所给0-1背包问题的子问题的最优值为\(m(i,j)\),即\(m(i,j)\)是 背包容量为j,可选择物品为1,2,…,i时0-1背包问题的 最优值。由0-1背包问题的最优子结构性质,可以建立计算 \(m(i,j)\)的递归式如下。

2.3.经典DP求解
n=5, c=10, w={2,2,6,5,4}, v={6,3,5,4,6}。
01背包为什么倒序:
我们每次计算\(dp[i][j]\)的时候都会需要\(dp[i-1][j-w[i]]\)的值。因为\(j-w[i]比j\)小,所以如果我们正序计算,那么\(dp[i][j-w[i]]就已\)经更新了 ,与状态转移方程不符。
import static java.lang.Math.max;
public class dome01 {
static int N = 1000;
//单个物品的重量和价值
int[] w = new int[]{0,2,2,6,5,4};
int[] v = new int[]{0,6,3,5,4,6};
//dp[i][j]表示前i件物品放到背包容量为j的最大价值
int [][] dp = new int[N][N];
public void solution() {
int n = 5;//物品的数量
int c = 10;//背包的容量
for(int i = 1; i <= n; i++)
{
for(int j = c; j >= 0; j--)
{
//如果当前放得下
if(w[i] <= j)
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]]+v[i]);
else
dp[i][j] = dp[i-1][j];
}
}
System.out.println(dp[n][c]);
}
}
dp数组

算法复杂度分析: 从m(i,j)的递归式容易看出,算法需要O(nc)计算时间。当背包容量c很 大时,算法需要的计算时间较多。例如,当c>2n时,算法需要Ω(n2n)计 算时间。
2.4.一维数组优化空间复杂度
由于我们在计算\(dp[i][j]\)时只用到了\(dp[i-1]\)部分的数据,也就是每一次只用到了两列的数据,那么我们就可以使用一个一维数组来替代原来的二维dp数组.
原状态转移方程:
\(f[i][j] = max(f[i - 1][j], f[i - 1][j - w[i]] + v[i])\)
一维数组优化后:
\(f[j] = max(f[j], f[j - w[i]] + v[i])\)
import static java.lang.Math.max;
public class dome01 {
static int N = 1000;
//单个物品的重量和价值
int[] w = new int[]{0,2,2,6,5,4};
int[] v = new int[]{0,6,3,5,4,6};
//dp[i][j]表示前i件物品放到背包容量为j的最大价值
int []dp = new int[N];
public void solution() {
int n = 5;//物品的数量
int c = 10;//背包的容量
for(int i = 1; i <= n; i++)
{
for(int j = c; j >= 0; j--)
{
//如果当前放得下
if(w[i] <= j)
dp[j] = max(dp[j], dp[j-w[i]]+v[i]);
}
}
System.out.println(dp[n][c]);
}
}
2.5.时间优化
一维数组只能优化空间复杂度,时间复杂度上并没有太多的优化
由\(dp(i,j)\)的递归式容易证明,在一般情况下,对每一个确定 的\(i(1≤i≤n)\),函数\(dp(i,j)\)是关于变量j的阶梯状单调不减函数。跳跃点是这一类函数的描述特征。在一般情况下,函数\(dp(i,j)\)由其全部跳跃点惟一确定。

对每一个确定的\(i(1≤i≤n)\), 用\(p[i]\)存储函数\(dp(i,j)\)的全部跳跃点。\(p[i]\)可依计算\(dp(i,j)\)的递归式递归地由\(p[i-1]\)计算,初始时\(p[0]={(0,0)}\)。
• \(dp(i,j)\)是由\(dp(i-1,j)\)与\(dp(i-1,j-wi )+vi\)作max运算得到的。
- 故\(dp(i,j)\)的全部跳跃点包含于\(dp(i-1,j)\)的跳跃点集\(p[i-1]\)与\(dp(i-1,j-wi )+vi\)的跳跃点集\(q[i-1]\)的并集中。
• 易知,\((s,t)∈q[i-1]\)当且仅当\(wi ≤s≤c\)且\((s-wi ,t-vi )∈p[i-1]\)。
• 因此,容易由\(p[i-1]\)确定跳跃点集\(q[i-1]\)如下:
\(q[i-1]=p[i-1]⊕(wi ,vi )={(j+wi ,m(i,j)+vi )|(j,m(i,j))∈p[i-1]}\)
• 另一方面,设\((a,b)和(c,d)\)是\(p[i-1]∪q[i-1]\)中的2个跳跃点, 则当\(c≥a且d<b\)时,\((c,d)受控于(a,b)\),从而\((c,d)不是p[i]\)中的跳跃点。除受控跳跃点外,\(p[i-1]∪q[i-1]\)中的其他跳跃点均为\(p[i]\)中的跳跃点
• 由此可见,在递归地由\(p[i-1]\)计算\(p[i]\)时,可先由\(p[i-1]\)计算出\(q[i-1]\),然后合并\(p[i-1]和q[i-1]\),并清除其中的受控 跳跃点得到\(p[i]\).


浙公网安备 33010602011771号