文章目录
在算法设计中,贪心算法(Greedy Algorithms)、动态规划与分治法(Divide and Conquer)并列,是最基础、最重要的算法设计思想。
贪心算法
动态规划
小偷背包示例
knapsack是背包的意思。
代码:
java
public class Knapsack01 {
/**
* 0-1 背包问题求解
* @param W 背包的总容量
* @param w 每个物品的重量数组
* @param v 每个物品的价值数组
* @return 背包能装下的最大价值
*/
public static int knapsack(int W, int[] w, int[] v) {
int n = w.length; // 物品的总个数
// 1. 定义状态数组:dp[i][j] 表示前 i 个物品在容量为 j 时的最大价值
// 行数是 n+1(包含0个物品的情况),列数是 W+1(包含容量为0的情况)
int[][] dp = new int[n + 1][W + 1];
// 2. 遍历物品(相当于填表的行)
// 从第 1 个物品开始,直到第 n 个物品
for (int i = 1; i <= n; i++) {
// 3. 遍历背包容量(相当于填表的列)
// 从容量 0 一直遍历到最大容量 W
for (int j = 0; j <= W; j++) {
// 核心决策:小偷面对第 i 个物品时的二选一
if (j < w[i - 1]) {
// 情况 A:当前背包容量 j 小于物品重量 w[i-1]
// 小偷装不下这个物品,只能放弃。
// 此时的最大价值等于【没有这个物品时】的最大价值
dp[i][j] = dp[i - 1][j];
} else {
// 情况 B:当前背包容量 j 大于等于物品重量 w[i-1]
// 小偷可以装下,此时需要比较"装"与"不装"哪个更划算
int notTake = dp[i - 1][j]; // 不装:继承上一行的结果
int take = dp[i - 1][j - w[i - 1]] + v[i - 1]; // 装:当前物品价值 + 剩余空间的最优解
// 取两者中的最大值
dp[i][j] = Math.max(notTake, take);
}
}
}
// 4. 返回最终结果:表格右下角的值,即前 n 个物品在容量 W 下的最大价值
return dp[n][W];
}
// 测试代码
public static void main(String[] args) {
int W = 5; // 背包容量为 5
int[] weights = {2, 3, 4}; // 物品重量
int[] values = {3, 4, 5}; // 物品价值
int maxValue = knapsack(W, weights, values);
System.out.println("小偷能偷到的最大价值是: " + maxValue);
}
}
代码与表格的对应关系
1、i对应表格的行(第几个物品)。注意数组下标从0开始,所以第i个物品的重量是wi-1。
2、j对应表格的列(当前的背包容量)。
3、dpij=Math.max(...)就是我们在表格里写下的那个"取最大值"的动作。
动态规划和贪心算法对比
| 对比维度 | 贪心算法 (Greedy) | 动态规划 (DP) |
|---|---|---|
| 核心思想 | 活在当下,只看眼前(局部最优) | 统筹全局,步步为营(全局最优) |
| 决策方式 | 做出选择后绝不后悔(无回溯) | 会考察所有可能的子问题,择优保留 |
| 是否记笔记 | 不需要,只记录当前状态 | 必须记(通过表格/备忘录避免重复计算) |
| 能否保证最优 | 不一定(取决于问题是否满足贪心性质) | 一定能(只要状态转移正确) |
| 运行效率 | 极高(通常只需排序或单次扫描) | 相对较低(通常需要填表,遍历所有子问题) |
| 空间消耗 | 极低(通常只需几个变量) | 较高(需要额外的内存存储 DP 表格) |
| 适用条件 | 贪心选择性质 + 最优子结构 | 重叠子问题 + 最优子结构 + 无后效性 |
| 通俗比喻 | 凭直觉走迷宫,选最顺眼的方向,绝不回头 | 走迷宫时记下每个路口的最优路线,稳扎稳打 |
分治法
分治法和归并排序经常划到一起。
分治法是核心思想,归并排序是其中一种具体的算法。