十八、动态规划(Dynamic Programming)
1、概念
和分治算法类似,动态规划也是将大问题划分为多个小问题解决,最终获取最优解的算法。
不同的是,动态规划所拆分的小问题之间存在依赖性,即下一步骤的解决依赖于上一步骤的解决。
可以通过填表法来逐步推进问题的解决,最终得到最优解。
2、背包问题
现有一个背包,存在容量上限,还有若干物品,分别有对应的体积和价值。要求在不超过背包容量上限的情况下,装入背包中的物品的总价值最大。问如何选择物品。
01背包:要求装入的物品不能重复,即每个物品的数量最多一个。
完全背包:装入的物品可以重复。
完全背包可以转换为01背包。
可以采用动态规划来解决:
- 定义背包容量上限、若干物品的体积数组和价值数组。
- 定义一个二维数组,用来表示填表法的结果。例如第一维度是体积,第二维度是价值,则每个元素(单元格)表示当前体积下的最大价值。
- 遍历二维数组(体积数组和价值数组),最终得到最优解(价值最大的元素)。
存在这样的关系:
- 二维数组的第一行和第一列的价值都是0。
- 当新的物品加入后超过了背包容量的上限时,则此元素的价值就是同等体积下的,上一个(物品的)价值。
- 如果可以继续物品,则此元素的价值(价值较大),要么是上一个物品加入后的价值(新的物品加入后超过了背包容量的上限),要么是新的物品的价值加上背包容量上限 减去 新的物品的体积,所对应的物品的价值(新的物品加入后不会超过背包容量的上限)。
物品详情
体积 | 价值 | |
---|---|---|
物品一 | 1 | 1500 |
物品二 | 4 | 3000 |
物品三 | 3 | 2000 |
示例
public void knapsackProblem() {
int maxVolume = 4;
int[] volumeArray = { 1, 4, 3 }; // 体积
int[] valueArray = { 1500, 3000, 2000 }; // 价值
int x = valueArray.length + 1;
int y = maxVolume + 1;
int[][] table = new int[x][y];
boolean[][] path = new boolean[x][y];
for (int i = 1; i < x; i++) { // 价值
for (int j = 1; j < y; j++) { // 体积
int index = i - 1; // 从0开始遍历
// 判断当前物品的体积是否超过当前遍历到的体积
if (volumeArray[index] > j) { // 超过了
table[i][j] = table[i - 1][j]; // 取相同体积下的,上一个价值的物品的价值
} else {
int a = table[i - 1][j];
// 当前物品价值 + 上一个价值中,体积之和能匹配当前遍历到的体积,的物品的价值
int b = valueArray[index] + table[i - 1][j - volumeArray[index]];
if (a >= b) { // 取价值加大的
table[i][j] = a;
} else {
table[i][j] = b;
path[i][j] = true; // 记录添加的新物品
}
}
}
}
// 遍历填表结果
for (int k = 0; k < x; k++) {
for (int l = 0; l < y; l++) {
System.out.print(table[k][l] + "\t");
}
System.out.println();
}
System.out.println();
// 获取最优解法所放入的物品
int m = x - 1;
int n = y - 1;
while (0 < m && 0 < n) {
if (path[m][n]) {
System.out.println("放入第" + m + "个物品");
n -= volumeArray[m - 1];
}
m--;
}
}
填表结果
体积 | 1 | 2 | 3 | 4 | |
---|---|---|---|---|---|
价值 | 0 | 0 | 0 | 0 | 0 |
1500 | 0 | 1500 | 1500 | 1500 | 1500 |
3000 | 0 | 1500 | 1500 | 1500 | 3000 |
2000 | 0 | 1500 | 1500 | 2000 | 3500 |