动态规划(Dynamic Programming,DP)是一种用于解决最优化问题的算法设计方法。它通过将大问题分解成小问题,并存储已经解决的小问题的解,以避免重复计算,从而提高算法的效率。
动态规划通常适用于以下几类问题:
- 最优子结构:问题的最优解可以由其子问题的最优解构成。
- 重叠子问题:问题可以被分解成较小的子问题,并且这些子问题在计算中会多次出现。
动态规划的基本步骤包括:
- 定义子问题:识别子问题,以便能够递归地求解它们。
- 构建状态转移方程:找出子问题之间的关系。
- 初始化状态:为子问题提供初始条件。
- 计算并存储中间结果:通过自底向上或者自顶向下的方式求解问题,保存结果以便复用。
- 返回最终结果:根据存储的结果返回所需的解。
动态规划实例:0-1 背包问题
问题描述
给定 n 个物品,每个物品有一个重量和一个价值。你有一个最大承载重量为 W 的背包。目标是选择物品组合,使得在不超过最大承载重量的情况下,背包中物品的总价值最大。注意:每个物品只能用一次。
示例
假设我们有以下物品和其对应的重量和价值:
物品 | 重量 | 价值 |
---|---|---|
1 | 1 | 1 |
2 | 3 | 4 |
3 | 4 | 5 |
4 | 5 | 7 |
最大承载重量 W = 7。
动态规划解法
-
定义状态:
- 令
dp[i][w]
表示前 i 个物品中,最大承载重量为 w 的情况下可以获得的最大价值。
- 令
-
初始化:
- 如果没有物品(i = 0),或者最大承载重量为 0(w = 0),则最大价值为 0。
dp[0][w] = 0
,dp[i][0] = 0
。
-
状态转移方程:
- 如果当前物品的重量大于 w(即
weight[i-1] > w
),则不能选择当前物品;此时dp[i][w] = dp[i-1][w]
。 - 如果当前物品的重量小于等于 w,选择当前物品或不选择:
dp[i][w]=max(dp[i−1][w],value[i−1]+dp[i−1][w−weight[i−1]])dp[i][w]=max(dp[i−1][w],value[i−1]+dp[i−1][w−weight[i−1]])
- 如果当前物品的重量大于 w(即
-
计算结果:
- 最终结果保存在
dp[n][W]
中,表示前 n 个物品,最大承载重量为 W 的最大价值。
- 最终结果保存在
实现代码
public class Knapsack {
public static int knapsack(int[] weights, int[] values, int W) {
int n = weights.length;
int[][] dp = new int[n + 1][W + 1];
// 初始化 DP 表格
for (int i = 0; i <= n; i++) {
for (int w = 0; w <= W; w++) {
if (i == 0 || w == 0) {
dp[i][w] = 0; // 无物品或无承载重量
} else if (weights[i - 1] <= w) {
// 选择当前物品或不选择
dp[i][w] = Math.max(dp[i - 1][w], values[i - 1] + dp[i - 1][w - weights[i - 1]]);
} else {
dp[i][w] = dp[i - 1][w]; // 不选择当前物品
}
}
}
return dp[n][W]; // 返回最大价值
}
public static void main(String[] args) {
int[] weights = {1, 3, 4, 5};
int[] values = {1, 4, 5, 7};
int W = 7;
System.out.println("Maximum value in Knapsack = " + knapsack(weights, values, W));
}
}
运行结果
Maximum value in Knapsack = 11
总结
动态规划是解决最优化问题的一种强大工具,通过将复杂问题分解成简单子问题,大大提高了解决效率。在0-1背包问题的例子中,我们可以明显看到动态规划如何有效地管理问题的状态,并通过简洁的状态转移关系得到最终的优化结果。理解动态规划的核心思想和应用场景,可以帮助开发者在遇到类似问题时提供高效的解决方案。