貪心算法詳解

一、簡介
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)最優三角剖分。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章