贪心算法详解

来源:互联网 发布:云端收银软件 编辑:程序博客网 时间:2024/06/15 15:45

一、简介
1.1 贪心算法基本思想
  贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的是在某种意义上的局部最优解。
  贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
1.2 贪心算法跟动态规划的区别
  贪心算法中“贪心”二字形象的说明了该算法的基本思想:贪心(每一步选择都是眼下的局部最优选择)。比如每次给你1张面额不定的纸币,共10次,你这么选?肯定是每次都要一张100元的。当你要拿第一张时,此时眼下最优的选择就是拿一张100的,不会管拿了之后会不会对后面的9张产生影响。这就是一种贪心,当然这种情况下的贪心选择也是最优的选择,因为局部最优导致了整体的最优。
  贪心算法常用于求解最优解问题,比动态规划思路简单,前提是要求问题满足贪心选择性质。
  形象的讲:贪心算法的每次选择就是只看当前的利益,不管当前的选择对后面选择的影响,所以如果当前的选择对之后的选择有影响时,这种选择就不一定最优了。
  而动态规划就是三思而后行,在考虑当前选择能产生的各种结果中选择一个最优的,想得多速度也就慢了。比如上面的例子,如果规定超过2张100,后面每张就都只会给1元的,那么按照贪心选择依然会前两张选择100的,后面就只能拿1元的,总共208元。按照动态规划,则会聪明的先选一张100元,后面每次都选择50元,总共550元。

二、贪心算法的基本要素
1、贪心选择
  贪心选择是指所求问题的整体最优解可以通过一系列局部最优的选择,即贪心选择来达到。这是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。通常可以首先证明问题的一个整体最优解,是从贪心选择开始的,而且作了贪心选择后,原问题简化为一个规模更小的类似子问题。然后,用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。
2、最优子结构
  当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题。

三、贪心算法的主要步骤
  (1)建立数学模型来描述问题;
  (2)把求解的问题分成若干个子问题;
  (3)对每一子问题求解,得到子问题的局部最优解;
  (4)把子问题的解局部最优解合成原来解问题的一个解。

四、例题分析
  下面是一个可以试用贪心算法解的题目,贪心解的确不错,可惜不是最优解。
背包问题:
  有一个背包,背包容量是M=150。有7个物品,物品可以分割成任意大小。要求尽可能让装入背包中的物品总价值最大,但不能超过总容量。
  物品 A B C D E F G
  重量 35 30 60 50 40 10 25
  价值 10 40 30 50 35 40 30
分析:
  目标函数: ∑pi最大
  约束条件是装入的物品总重量不超过背包容量:∑wi<=M( M=150)
优化策略:
  (1)根据贪心的策略,每次挑选价值最大的物品装入背包,得到的结果是否最优?
  (2)每次挑选所占重量最小的物品装入是否能得到最优解?
  (3)每次选取单位重量价值最大的物品,成为解本题的策略。
   值得注意的是,贪心算法并不是完全不可以使用,贪心策略一旦经过证明成立后,它就是一种高效的算法。
  贪心算法还是很常见的算法之一,这是由于它简单易行,构造贪心策略不是很困难。
  可惜的是,它需要证明后才能真正运用到题目的算法中。
  一般来说,贪心算法的证明围绕着:整个问题的最优解一定由在贪心策略中存在的子问题的最优解得来的。
  对于例题中的3种贪心策略,都是无法成立(无法被证明)的,具体原因如下:
  (1)贪心策略:选取价值最大者。反例:
    W=30
     物品:A B C
     重量:28 12 12
     价值:30 20 20
  根据策略,首先选取物品A,接下来就无法再选取了,可是,选取B、C则更好。
  (2)贪心策略:选取重量最小。它的反例与第一种策略的反例差不多。
   (3)贪心策略:选取单位重量价值最大的物品。反例:
    W=30
     物品:A B C
     重量:28 20 10
     价值:28 20 10
  根据策略,三种物品单位重量价值一样,程序无法依据现有策略作出判断,如果选择A,则答案错误。

五、leetcode中关于贪心算法的实例
5.1 Jump Game
(1)题意
  给你一个数组,数组的每个元素表示你能前进的最大步数,最开始时你在第一个元素所在的位置,之后你可以前进,问能不能到达最后一个元素位置。例如:A = [2, 3, 1, 1, 4], return true。此时一种走法是 0 - 2 - 3 - 4,还有一种走法是 0 - 1 – 4。
(2)解题思路
  使用贪心算法,不管每一步怎么跳,我都跳到最后,跳到不能跳为止。 比如我们用一个变量max_reach,来记录我能跳到的最后的位置。对第i步来说,从第i个位置出发的最远是nums[i]+i那么我们的max_reach =max(max_reach,nums[i]+i) 如果在某一步i>G,也就是说,前面能跳到的最远距离跳不到i,那就肯定失败。 如果最终能跳到最后一步就返回成功。
(3)python代码

class Solution(object):    def canJump(self, nums):        max_reach, n = 0, len(nums)        for i, x in enumerate(nums):            if max_reach < i:                     return False            if max_reach >= n - 1:                     return True            max_reach = max(max_reach, i + x)

5.2 Container With Most Water
(1)题目
  在二维坐标系中,(i, ai) 表示 从 (i, 0) 到 (i, ai) 的一条线段,任意两条这样的线段和 x 轴组成一个木桶,找出能够盛水最多的木桶,返回其容积。
(2)解题思路
  用两个指针从两端开始向中间靠拢,如果左端线段短于右端,那么左端右移,反之右端左移,知道左右两端移到中间重合,记录这个过程中每一次组成木桶的容积,返回其中最大的。
  合理性解释:当左端线段L小于右端线段R时,我们把L右移,这时舍弃的是L与右端其他线段(R-1, R-2, …)组成的木桶,这些木桶是没必要判断的,因为这些木桶的容积肯定都没有L和R组成的木桶容积大。这是因为木桶的容积由L和R的最小值和(i-j)所决定,两段取其小有效。
(3)python代码

class Solution(object):    def maxArea(self, height):        size=len(height)        maxm=0        j=0        k=size-1        while j<k:            if height[j]<=height[k]:                maxm=max(maxm,height[j]*(k-j))                j+=1            else:                maxm=max(maxm,height[k]*(k-j))                k-=1        return maxm

六、用贪心算法实现的经典实例
  (1)背包问题(物体可切分时的0-1背包问题);
  (2)Huffman编码;
  (3)单源最短路径;
  (4)Prim算法;
  (5)Kruskal算法;
  (6)最优三角剖分。