1. 动态规划算法的介绍
动态规划(Dynamic Programming)算法的核心思想是:
- 先将待求解问题分解成若干个子问题。各个子问题不是互相独立的(分治法各个子问题是互相独立的),即下一个子问题的求解是建立在上一个子问题的解的基础上,进行进一步的求解
- 再求解子问题,然后从这些子问题的解得到原问题的最优解
2. 背包问题介绍
背包问题是动态规划算法的一个实际应用。一个背包,容量为4磅, 现有如下物品:
物品 | 重量 | 价格 |
---|---|---|
吉他(G) | 1 | 1500 |
音响(S) | 4 | 3000 |
电脑(L) | 3 | 2000 |
有如下要求:
- 要求达到的目标为装入的背包的总价值最大,并且重量不超出背包容量
- 要求装入的物品不能重复
这里的问题属于01背包,即每个物品最多放一次。另一种是完全背包,每个物品可以放无限次,完全背包可以转化为01背包
3. 通过填表来逐步推进背包问题
第一步:先将问题分解成一个个小的问题,即假设存在背包容量大小分别为1、2、3、4的各种测试容量的背包(分配测试容量的规则为最小物品重量的整数倍,但不超过背包的实际容量)。如下所示,注意这里物品的顺序不重要,因为是一个动态规划的过程,求的是最终的放入最大物品总价值
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他(G) | 0 | ||||
音响(S) | 0 | ||||
电脑(L) | 0 |
如果背包不能放东西,则放入背包的物品总价值为0,即第一列全为0;如果没有物品,则放入背包的物品总价值也为0,即第一行全为0
第二步:对于第二行(p = 1), 目前只有吉他可以选择,如下所示
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他(G) | 0 | 1500(G) | 1500(G) | 1500(G) | 1500(G) |
音响(S) | 0 | ||||
电脑(L) | 0 |
第三步:对于第三行(p = 2), 目前存在吉他和音响可以选择, 如下所示
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他(G) | 0 | 1500(G) | 1500(G) | 1500(G) | 1500(G) |
音响(S) | 0 | 1500(G) | 1500(G) | 1500(G) | 3000(S) |
电脑(L) | 0 |
第四步:对于第四行(p = 3), 目前存在吉他和音响、电脑可以选择, 如下所示
物品 | 0磅 | 1磅 | 2磅 | 3磅 | 4磅 |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | |
吉他(G) | 0 | 1500(G) | 1500(G) | 1500(G) | 1500(G) |
音响(S) | 0 | 1500(G) | 1500(G) | 1500(G) | 3000(S) |
电脑(L) | 0 | 1500(G) | 1500(G) | 2000(L) | 3500(L+G) |
4. 背包问题的思路分析
用weights数组保存各个物品的重量;用values数组保存各个物品的价值;用dynamicTables二维数组保存动态规划表的逐步推导数据;dynamicTables[p][w]表示前p个物品放入测试容量为w的背包的最大物品总价值,p表示行,即物品所在的行index,w表示列,同时表示背包的测试容量
对不同的物品和不同的背包测试容量进行双层遍历,有如下结果:
- dynamicTables[p][0] = dynamicTables[0][w] = 0:表示填入表第一列和第一行都是0
- 当weight[p] > w时:dynamicTables[p][w] = dynamicTables[p - 1][w]:表示当前物品的重量大于背包的测试容量,当前物品不能放入背包,所以取前面的物品的最大物品总价值
- 当weight[p] <= w时: dynamicTables[p][w] = max{dynamicTables[p - 1][w], weight[p] + dynamicTables[p - 1][w - weight[p]]}:表示当前物品的重量小于等于背包的测试容量,当前物品能放入背包,但不确定放入当前物品是否会让背包的最大物品总价值变大,所以取max{【前面的物品的最大物品总价值】, 【背包先放入当前物品,加上背包剩余的容量放前面的物品的最大物品总价值】}
5. 背包问题的动态规划算法程序实现
public class KnapsackProblem {
public static void main(String[] args) {
// 保存物品的重量
int[] weights = {1, 4, 3};
// 保存物品的价值
int[] values = {1500, 3000, 2000};
// 背包的实际容量
int knapsackWeight = 4;
// 物品的种类
int productCategoryNum = weights.length;
// 保存动态规划表的逐步推导数据
int[][] dynamicTables = new int[productCategoryNum + 1][knapsackWeight + 1];
// 保存物品放入背包的情况。0表示未放入物品,1表示放入物品
int[][] putPaths = new int[productCategoryNum + 1][knapsackWeight + 1];
// 填入表第一列和第一行都是0
for (int p = 0; p < dynamicTables.length; p++) {
dynamicTables[p][0] = 0;
}
for (int w = 0; w < dynamicTables[0].length; w++) {
dynamicTables[0][w] = 0;
}
// 根据公式进行动态规划
for (int p = 1; p < dynamicTables.length; p++) {
for (int w = 1; w < dynamicTables[p].length; w++) {
// 当前物品的重量大于背包的测试容量
if (weights[p - 1] > w) {
dynamicTables[p][w] = dynamicTables[p - 1][w];
} else {
// 当前物品的重量大小于等于背包的测试容量
// dynamicTables[p][w] = Math.max(dynamicTables[p - 1][w], values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]]);
// 为了记录物品放到背包的情况,不能直接的使用上面的公式,使用if-else来替换
if (dynamicTables[p - 1][w] < values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]]) {
dynamicTables[p][w] = values[p - 1] + dynamicTables[p - 1][w - weights[p - 1]];
// 当前物品放入背包,使背包的物品总价值变大,则表示可以放入该物品,并同时进行记录
putPaths[p][w] = 1;
} else {
dynamicTables[p][w] = dynamicTables[p - 1][w];
}
}
}
}
// 输出dynamicTables
for (int p = 0; p < dynamicTables.length; p++) {
for (int w = 0; w < dynamicTables[p].length; w++) {
System.out.print(dynamicTables[p][w] + " ");
}
System.out.println();
}
System.out.println("=============倒序输出使背包物品总价值最大的物品===============");
// 行的最大index
int p = putPaths.length - 1;
// 列的最大index
int w = putPaths[0].length - 1;
// 从putPaths的最后开始找
while (p > 0 && w > 0) {
// 表示找到
if (putPaths[p][w] == 1) {
System.out.printf("第%d个物品放入到背包\n", p);
// 查找到了一个物品,则从前面的物品的最大物品总价值中进行查找
w -= weights[p - 1];
}
// 继续从前面的物品进行查找
p--;
}
}
}
运行程序,结果如下:
0 0 0 0 0
0 1500 1500 1500 1500
0 1500 1500 1500 3000
0 1500 1500 2000 3500
=============倒序输出使背包物品总价值最大的物品===============
第3个物品放入到背包
第1个物品放入到背包