动态规划和贪心算法都是用来求最优化问题,且二者都必须具有最有子结构。贪心算法可以解决的问题,动态规划都能解决,可以说,贪心算法是动态规划的一个特例。
贪心算法和动态规划最大的不同在于,它并不是首先寻找子问题的最优解,然后在其中进行选择,而是首先做一次贪心选择——在当时(局部)看来最有选择——然后求解选出的子问题,从而不必费心求解所有可能相关的子问题。
动态规划
动态规划)与分治法相似,都是通过组合子问题的解来求解原问题。分治法将问题划分为互不相交的子问题,递归求解子问题,再将它们的解组合起来,求出原问题的解。与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的子子问题(子问题的求解释递归进行的,将其划分为更小的子子问题)。这种情况下,动态规划对公共子子问题只求一次解,而分治法会反复求解公共子子问题。
我们通常按如下四个步骤来设计一个动态规划算法:
刻画一个最优解的结构特征(如果一个问题的最优解包含其子问题的最优解,就称此问题具有最有子结构性质)。
递归的定义最优解的值。
计算最优解的值,通常采用自底向上的方法。
利用计算出的信息构造一个最优解。
eg:最长公共子序列(LCS)问题。
贪心算法
求解最优化问题的算法通常需要经过一系列的步骤,在每个步骤都面临多种选择。对于许多最优化问题,使用动态规划算法求最优解有些杀鸡用牛刀了,可以使用更简单更优化的算法,即贪心算法(greedy algorithm)。它在每一步都做出当时看起来最佳的选择。也就是说,它总是做出局部最优的选择,寄希望这样的选择能导致全局最优解。贪心算法并不保证得到最优解,但对很多问题确实可以求得最优解。
可以按如下步骤 设计贪心算法:
将最优化问题转化为这样的形式:对其作出一次选择后,只剩下一个子问题需要求解。
证明作出贪心选择后,原问题总是存在最优解,即贪心选择总是安全的。
证明作出贪心选择后,剩余的子问题满足性质:其最优解与贪心选择组合即可得到原问题的最优解,这样就得到了最优子结构。
贪心选择性质(greedy-choice property):我们可以通过作出局部最优(贪心)选择来构造全局最优解。换句话说,当进行选择时,我们直接作出在当前问题中看来最优的选择,而不必考虑子问题的解。
这也是贪心算法与动态规划的不同之处。在动态规划方法中,每个步骤都要进行一次选择,但选择通常依赖于子问题的解。因此,我们通常以一种自底向上的方式求解动态规划问题,先求解娇小的子问题,然后是交大的子问题(我们也可以自顶向下求解,但需要备忘机制。当然,即使算法是自顶向下进行计算,我们仍然需要先求解子问题再进行选择)。在贪心算法中,我们总是做出当时看来最佳的选择,然后求解剩下的唯一子问题。贪心算法进行选择时可能依赖于之前做出的选择,但不依赖于任何将来的选择或是子问题的解。因此,与动态规划先求解子问题才能进行第一次选择不用,贪心算法在进行第一次选择之前不求解任何子问题。一个动态规划算法是自底向上进行计算的,而一个贪心算法通常是自顶向下的,进行一次又一次选择,将给定问题实例变得更小。
eg:赫夫曼编码、最小生成树算法(Kruskal算法和Prim算法)
几个经典贪心问题
一、背包相关问题
1.最优装载问题:给出N个物体,有一定重量。请选择尽量多的物体,使总重量不超过C。
解法:只关心数量多,便把重量从小到大排序,依次选,直到装不下。
二、区间相关问题
1.选择不相交区间:数轴上有N个开区间(Li,Ri),请选择尽量多个区间,并保证这些区间两两没有公共点。
三、Huffman编码
最优编码问题:给出N个字符的频率 ci ,给每个字符赋予一个01编码串,使得任意一个字符的编码不是另一个字符编码的前缀,而且编码后总长度(每个字符的频率与编码长度乘积的总和)尽量小