目录
[一、核心思想的区别:看眼前 vs 看全局](#一、核心思想的区别:看眼前 vs 看全局)
[1. 贪心算法:只顾眼前](#1. 贪心算法:只顾眼前)
[2. 动态规划:着眼全局](#2. 动态规划:着眼全局)
[1. 动态规划的要求:最优子结构](#1. 动态规划的要求:最优子结构)
[2. 贪心算法的要求:贪心选择性](#2. 贪心算法的要求:贪心选择性)
[3. 结论](#3. 结论)
在算法的世界里,动态规划(Dynamic Programming, DP) 和 贪心算法(Greedy Algorithm) 是两道最常见的门槛。很多初学者在刷题时都会有这样的困惑:
"这道题我明明用贪心就能过,为什么答案说要动态规划?"
"它们俩长得好像,到底有什么区别?谁更厉害?"
今天,我们就用一篇文章,彻底讲清楚这两者的关系。如果你能耐心看完,你会发现一个颠覆认知的结论:贪心算法,其实是动态规划的一种特殊情况。
一、核心思想的区别:看眼前 vs 看全局
在深入"谁包含谁"之前,我们先从思维模式上感受一下它们的差异。
1. 贪心算法:只顾眼前
贪心算法的核心是 "短视"。它在每一步决策时,只选择当前看起来最好的选项,选完就不再回头(不可回溯)。
思维模式:"我现在就要选一个看起来最好的,选完就不后悔,也不管后面会发生什么。"
适用条件 :问题必须满足 贪心选择性------即每一步的局部最优能推导出全局最优。
优点:效率极高,代码简单,通常是 O(n) 或 O(n log n)。
2. 动态规划:着眼全局
动态规划的核心是 "记仇"。它在做决策时,会考虑到所有可能的选项,并记住每种选择带来的后续影响。它通过穷举所有可能性,再从中挑选最优解。
思维模式:"我现在不知道选谁最好,所以我先试试所有可能,看看哪个选项能让我在未来走得更远,我才选它。"
适用条件 :问题必须满足 最优子结构------即全局最优解包含子问题的最优解。
优点:通用性强,只要满足最优子结构,DP 几乎都能解。
二、一个经典类比:从北京去上海
假设你想从北京开车去上海,中间要经过多个城市。每条路的路况和距离都不一样。
贪心算法:站在北京,我不管终点在哪,先找一条离北京最近的高速出口开出去,然后到了下一个城市,再找离那个城市最近的路......直到到达上海。
- 结果:可能绕远路,因为只看眼前,没看全局。
动态规划:站在北京,我先查地图,看看走哪条路虽然现在远一点,但到了下一个城市后,后续的路能更快到上海。它会把所有路径的可能性都算一遍,选出总路程最短的那条。
- 结果:保证全局最优。
在这个例子中,除非整个路网的结构非常特殊(比如 Dijkstra 算法适用的非负权图),否则贪心很容易失败。而动态规划,永远能找到最短路径。
三、谁包含谁?从数学上找答案
这是本文最核心的部分。从数学定义和算法设计的角度来看:
1. 动态规划的要求:最优子结构
如果一个问题的最优解包含其子问题的最优解,那么这个问题就具有最优子结构。这是使用动态规划的前提。
2. 贪心算法的要求:贪心选择性
如果一个问题不仅具有最优子结构,而且我们还可以通过一系列局部最优的选择 (即贪心选择)来构造全局最优解,那么这个问题就具有贪心选择性。
3. 结论
贪心选择性质是比最优子结构更强的条件。
如果一个算法问题满足贪心选择性,那么它必然满足最优子结构。
但反过来,满足最优子结构的问题,不一定满足贪心选择性。
因此,在算法分类的层级上:
贪心算法是动态规划的一个子集。
或者说,动态规划包含了贪心算法。
你可以把贪心算法看作是一种经过筛选的、效率极高的、不需要回溯穷举的动态规划。
四、验证结论:贪心能解的,DP都能解吗?
根据上面的包含关系,我们可以得出一个重要推论:
如果一个算法问题可以用贪心算法解决,那么它一定也可以用动态规划解决。
这个结论成立,是因为贪心能解的题必然满足最优子结构,而动态规划正是基于最优子结构去穷举所有可能。DP 的计算过程中,自然会包含贪心算法选出的那条路径,并且最终也能选出那条路径作为答案。
五、既然都能解,为什么还要区分?
这就涉及到算法设计的取舍 了。我们用一道经典例题来说明:零钱兑换问题。
场景
你需要用最少的硬币数量凑出 16 元钱,假设有无限量的硬币可用。
情况 A:硬币面额是 [1, 5, 10, 20] (正常货币体系)
贪心做法 :先用最大的 10 元(剩 6),再用 5 元(剩 1),再用 1 元。结果:10+5+1=16,共 3 枚。
动态规划做法 :计算所有凑出 16 元的组合,找到最少硬币数。结果:也是 3 枚。
结论 :在这个货币体系下,贪心和动态规划算出的答案一模一样。但是贪心算法可能瞬间就算出来了(O(n)),而动态规划可能需要遍历很多组合(O(n×m))。显然,这里用贪心更好。
情况 B:硬币面额是 [1, 5, 11] ,你需要凑出 15 元。
贪心做法 :先用 11 元(剩 4),再用 4 个 1 元。结果:11+1+1+1+1=15,共 5 枚。
动态规划做法 :计算发现,用 5+5+5=15,只要 3 枚。
结论 :贪心算法在这里失效了 (因为它只看到了眼前,选了 11,没看到选 5 在未来更优)。而动态规划依然能算出正确答案 3 枚。
总结取舍
在正确性上:动态规划一定能解,因为它穷举了所有可能性,不会漏掉贪心找到的那条路径。
在实际应用中 :如果一个题确定 能用贪心解(比如题目有严格的数学特征,或者你做过证明),我们不会用动态规划。因为贪心是动态规划的一种高效率的"捷径"。
六、实际刷题中,如何选择?
理解了它们的包含关系后,在实际刷题或开发中,可以按照这个思路去选择:
1、先看能不能用贪心:
- 如果你能证明(或者凭经验知道)题目具有贪心选择性,直接用贪心。代码简单,效率高。
- 常见的贪心经典题:活动选择、Huffman 编码、最小生成树(Prim/Kruskal)、Dijkstra 算法(非负权图)。
2、不确定时,或者题目较复杂:
如果不能证明贪心是对的,或者题目涉及到状态的组合(比如背包问题、路径问题),那就老老实实用动态规划。
动态规划虽然时间复杂度可能高一些,但能保证得到正确答案。
七、一句话总结
贪心算法是加了"只看眼前"这个限制条件的动态规划。
所以动态规划是更通用的父集,贪心是其特例。
动态规划是"大道",虽然绕远但一定到终点。
贪心是"小道",只有特定的路(题目)才能走,但一旦能走,就比大道快得多。
希望这篇文章能帮你彻底理清动态规划和贪心算法的关系。如果你在刷题时遇到类似的困惑,欢迎留言讨论!