概述———动态规划
提出人:理查德·贝尔曼
本质:一张表格处理方法
内容:把原问题分解为若干子问题,自底向上先求解最小子问题,
把结果储存在表格中,求解大的子问题时直接从表格中查询
小的子问题的解,以避免重复计算,从而提高效率。
一、动态规划求解原理
适用范围:问题需要具备3个性质———最优子结构、子问题重叠、无后效性。
最优子结构指问题最优解包含其子问题的最优解,是使用动态规划的基本条件。
三要素:状态、阶段、决策
状态表示———将问题发展到各个阶段时所处的各种客观情况用不同的状态表示出来,确定状态和状态变量。状态的选择要满足无后效性。
阶段划分———按照问题的时特征或空间特征,将问题划分若干阶段。划分后的阶段一定是有序或可排序的,否则问题无法求解。
状态转移———根据上一个的状态和决策导出本阶段的状态。
边界条件———需要确定初始条件和边界条件
求解目标———确定问题的求解目标,根据状态转移方程的递推结果得到求解目标。
二、背包问题
背包问题:在一个有容积或重量限制的背包中放入物品,物品有体积、重量、价值等属性,要求在满足背包限制的情况下放置物品,使背包中物品的价值之和最大。
1.01背包
N个物品,每个物品都有重量Wi和价值Vi,每个物品只有一个(有——1,无——0)
背包容量W, 求解在不超过背包容量的情况下将哪些物品放入背包,才可以使背包中物品的价值之和最大;
状态表示:c[I][j]表示将前i种物品放入容量为j的背包中获得的最大价值。
j<w[i], c[i][j]=c[i-1][j]
j>=w[i], c[i][j] = max{c[i-1][j], c[i-1][j - W[i]] + V[i]}
Eg.
5个物品,背包容量10
重量:2,5,4,2,3
价值:6,3,5,4,6
求背包容量内物品价值和最大
c[][] | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
2 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 9 | 9 | 9 | 9 |
3 | 0 | 0 | 6 | 6 | 6 | 6 | 11 | 11 | 11 | 11 | 11 |
4 | 0 | 0 | 6 | 6 | 10 | 10 | 11 | 11 | 15 | 15 | 15 |
5 | 0 | 0 | 6 | 6 | 10 | 12 | 12 | 16 | 16 | 17 | 17 |
int n = 5;
int[] w = {2, 5, 4, 2, 3};
int[] v = {6,3,5,4,6};
int R = 10;
int[][] dp = new int[n+1][R+1];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= R; j++) {
if (j < w[i-1]) {
dp[i][j] = dp[i-1][j];
} else {
dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j - w[i-1]] + v[i-1]);
}
}
}
System.out.println(dp[n][R]);
// 最优解构造
int[] x = new int[n];
for (int i = 5; i > 0; i--) {
if (dp[i-1][R] < dp[i][R]) {
x[i-1] = 1;
R-=w[i-1];
} else {
x[i-1] = 0;
}
}
System.out.println(IntStream.range(0, n).filter(t -> x[t] == 1)
.map(t -> w[t]).boxed().collect(Collectors.toList()));
逆推算法优化(正推会有重复,数据覆盖引起)
int n = 5;
int[] w = {2, 5, 4, 2, 3};
int[] v = {6,3,5,4,6};
int R = 10;
int[] dp = new int[R+1];
for (int i = 0; i < n; i++) {
for (int j = R; j >= w[i]; j--) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
System.out.println(dp[R]);
2、完全背包
N个物品,每个物品都有重量Wi和价值Vi,其数量没有限制
背包容量W
求解在不超过背包容量的情况下将哪些物品放入背包,才可以使背包中物品的价值之和最大;
// 正推算法,从i阶段到i阶段
int n = 5;
int[] w = {1,2,3,4,5};
int[] v = {5,4,3,2,1};
int R = 10;
int[] dp = new int[R+1];
for (int i = 0; i < n; i++) {
for (int j = w[i]; j <= R; j++) {
dp[j] = Math.max(dp[j], dp[j - w[i]] + v[i]);
}
}
System.out.println(dp[R]);
3、多重背包
N个物品,每个物品都有重量Wi和价值Vi,每种物品的数量都可以大于1但有数量限制
背包容量W,求解在不超过背包容量的情况下将哪些物品放入背包,才可以使背包中物品的价值之和最大;
(1)、暴力拆分(速度堪忧)
将第i种物品看作Ci种独立物品,每种物品只有一个———转成01背包问题
先找到所有物品,即先循环每种物品,再循环每种物品中每一个物品,最后逆序容量
(2)、二进制拆分
当c[I]*w[I]>=R,当完全背包问题
否则,采用二进制拆分,将c[i]个物品拆分成若干个新物品
存在最大整数P,使得2^0+2^1+·····+2^p<=C[i],剩余部分R[i]=c[i] - (2^0+2^1+·····+2^p);
(分成P+2堆原因需要思考下,是因为这这些能组合任意可能组合,如3=2^0+2^1等)
(3)、数组优化
用num[j]数组记录容量为j时放入了多少个i种物品,以满足物品数量限制。
完全背包+数组数量限制
4.分组背包(价值最大化)
给定n组物品,第i组有ci个物品,第i组的第j个物品有重量Wij和价值Vij背包容量为W,
在不超过背包容量的情况下每组最多选择一个物品,如何选择物品使价值和最大
将每个组看作一个物品,是为01背包问题,组内内部循环找最合适的物品
5.混合背包
01背包、完全背包、多重背包问题混合使用,具体问题具体判断