貪心算法

貪心算法有不少經典的應用,好比霍夫曼編碼(Huffman Coding)、Prim 和 Kruskal 最小生成樹算法、還有 Dijkstra 單源最短路徑算法。最小生成樹算法和最短路徑算法咱們後面會講到,因此咱們今天講下霍夫曼編碼,看看它是如何利用貪心算法來實現對數據壓縮編碼,有效節省數據存儲空間的web

什麼是貪心算法?

關於貪心算法,咱們先看一個例子。算法

假設咱們有一個能夠容納 100kg 物品的揹包,能夠裝各類物品。咱們有如下 5 種豆子,每種豆子的總量和總價值都各不相同。爲了讓揹包中所裝物品的總價值最大,咱們如何選擇在揹包中裝哪些豆子?每種豆子又該裝多少呢?svg

在這裏插入圖片描述
實際上,這個問題很簡單,就是按照單價從大到小來裝就好了,對吧?編碼

以上本質上藉助的就是貪心算法。結合這個例子,我總結一下貪心算法解決問題的步驟,咱們一塊兒來看看。spa

  • 第一步,當咱們看到這類問題的時候,首先要聯想到貪心算法:針對一組數據,咱們定義了限制值和指望值,但願從中選出幾個數據,在知足限制值的狀況下,指望值最大

類比到剛剛的例子,限制值就是重量不能超過 100kg,指望值就是物品的總價值。這組數據就是 5 種豆子。咱們從中選出一部分,知足重量不超過 100kg,而且總價值最大。code

  • 第二步,咱們嘗試看下這個問題是否能夠用貪心算法解決:每次選擇當前狀況下,在對限制值同等貢獻量的狀況下,對指望值貢獻最大的數據。

類比到剛剛的例子,咱們每次都從剩下的豆子裏面,選擇單價最高的,也就是重量相同的狀況下,對價值貢獻最大的豆子。xml

  • 第三步,咱們舉幾個例子看下貪心算法產生的結果是不是最優的。大部分狀況下,舉幾個例子驗證一下就能夠了。嚴格地證實貪心算法的正確性,是很是複雜的,須要涉及比較多的數學推理。

實際上,用貪心算法解決問題的思路,並不總能給出最優解。blog

我來舉一個例子。在一個有權圖中,咱們從頂點 S 開始,找一條到頂點 T 的最短路徑(路徑中邊的權值和最小)。貪心算法的解決思路是,每次都選擇一條跟當前頂點相連的權最小的邊,直到找到頂點 T。按照這種思路,咱們求出的最短路徑是 S->A->E->T,路徑長度是 1+4+4=9。
在這裏插入圖片描述
很明顯,這不是最短路徑,最短路徑是S->B->D->T,那麼這是爲何吶?排序

在這個問題上,貪心算法不工做的主要緣由是,前面的選擇,會影響後面的選擇。若是咱們第一步從頂點 S 走到頂點 A,那接下來面對的頂點和邊,跟第一步從頂點 S 走到頂點 B,是徹底不一樣的。因此,即使咱們第一步選擇最優的走法(邊最短),但有可能由於這一步選擇,致使後面每一步的選擇都很糟糕,最終也就無緣全局最優解了。token

貪心算法實戰分析

1. 分發糖果(Leetcode題目)

老師想給孩子們分發糖果,有 N 個孩子站成了一條直線,老師會根據每一個孩子的表現,預先給他們評分。

你須要按照如下要求,幫助老師給這些孩子分發糖果:

每一個孩子至少分配到 1 個糖果。
相鄰的孩子中,評分高的孩子必須得到更多的糖果。
那麼這樣下來,老師至少須要準備多少顆糖果呢?

示例 1:

輸入: [1,0,2]
輸出: 5
解釋: 你能夠分別給這三個孩子分發 二、一、2 顆糖果。
示例 2:

輸入: [1,2,2]
輸出: 4
解釋: 你能夠分別給這三個孩子分發 一、二、1 顆糖果。
第三個孩子只獲得 1 顆糖果,這已知足上述兩個條件。

貪心思想分析:指望是讓糖果數目最小,限制就是那兩個條件,若是下一個要大,那天然最好的選擇就是多給一個糖果最好,因此在這裏咱們先不考慮其餘的,就給他一個糖果就好.這樣遍歷過去主要是沒法處理1,2,3,3,2,1這類數據,那麼再逆着遍歷貪心就好了(這個比較難想到)

class Solution {
public:
    int candy(vector<int>& rat) {
        //貪心
        int len =  rat.size();
        vector<int> candy(len,1);
        candy[0]=1;
        for(int i=0 ;i< len-1 ;i++){
            if(rat[i+1] > rat[i] )
                candy[i+1] =candy[i]+1; 
        }
        //逆着貪心
        for(int i=len-1 ;i > 0;i--){
            if(rat[i-1] > rat[i] && candy[i-1] <=candy[i])
                candy[i-1] = candy[i]+1; 
        }
        int result = 0 ;
        for(auto i:candy)
            result+=i;
        return result;
    }
};

2. 2. 錢幣找零(有面值爲1的能夠"考慮"貪心,沒有就 DP )

這個問題在咱們的平常生活中更加廣泛。假設咱們有 1 元、2 元、5 元、10 元、20 元、50 元、100 元這些面額的紙幣,它們的張數分別是 c一、c二、c五、c十、c20、c50、c100。咱們如今要用這些錢來支付 K 元,最少要用多少張紙幣呢?

生活中,咱們確定是先用面值最大的來支付,若是不夠,就繼續用更小一點面值的,以此類推,最後剩下的用 1 元來補齊。

在貢獻相同指望值(紙幣數目)的狀況下,咱們但願多貢獻點金額,這樣就可讓紙幣數更少,這就是一種貪心算法的解決思路。直覺告訴咱們,這種處理方法就是最好的。實際上,要嚴謹地證實這種貪心算法的正確性,須要比較複雜的、有技巧的數學推導,我不建議你花太多時間在上面,不過若是感興趣的話,能夠本身去研究下。

3. 3. 區間覆蓋

假設咱們有 n 個區間,區間的起始端點和結束端點分別是 [l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。咱們從這 n 個區間中選出一部分區間,這部分區間知足兩兩不相交(端點相交的狀況不算相交),最多能選出多少個區間呢(這是指望)?
在這裏插入圖片描述
這個問題的處理思路稍微不是那麼好懂,不過,我建議你最好能弄懂,由於這個處理思想在不少貪心算法問題中都有用到,好比任務調度、教師排課等等問題。 (這個應該和動態規劃放在一塊兒比較一下)

這個問題的解決思路是這樣的:咱們假設這 n 個區間中最左端點是 lmin,最右端點是 rmax。這個問題就至關於,咱們選擇幾個不相交的區間,從左到右將 [lmin, rmax] 覆蓋上。咱們按照起始端點從小到大的順序對這 n 個區間排序。

咱們每次選擇的時候,左端點跟前面的已經覆蓋的區間不重合的,右端點又儘可能小的,這樣可讓剩下的未覆蓋區間儘量的大,就能夠放置更多的區間。這實際上就是一種貪心的選擇方法。

在這裏插入圖片描述
leetcode題目:

給定一個區間的集合,找到須要移除區間的最小數量,使剩餘區間互不重疊。
注意:
能夠認爲區間的終點老是大於它的起點。
區間 [1,2] 和 [2,3] 的邊界相互「接觸」,但沒有相互重疊。
示例 1:

輸入: [ [1,2], [2,3], [3,4], [1,3] ]

輸出: 1

解釋: 移除 [1,3] 後,剩下的區間沒有重疊。
示例 2:

輸入: [ [1,2], [1,2], [1,2] ]

輸出: 2

解釋: 你須要移除兩個 [1,2] 來使剩下的區間沒有重疊。
示例 3:

輸入: [ [1,2], [2,3] ]

輸出: 0

解釋: 你不須要移除任何區間,由於它們已是無重疊的了。

class Solution
{
  public:
    int eraseOverlapIntervals(vector<Interval> &vals)
    {
        if(vals.size() == 0 ) 
            return 0;
        //指望是移除的數量最小,那麼仍是找最短的區間去覆蓋整個區間便可
        sort(vals.begin(), vals.end(), [](const Interval &a, const Interval &b) {
            return a.start < b.start;
        });
        int result = 0;
        stack<Interval>  ss ;
        ss.push(vals[0]);
        for (int i = 1; i < vals.size(); i++)
        {
            Interval tmp = ss.top();
            if (vals[i].end < tmp.end){ //前一個比當前的區間長
                result+=1;
                ss.pop();//把上一個淘汰,下面天然會push進去
            }
            else if(vals[i].start < tmp.end && vals[i].end >= tmp.end){ //區間相交
                result += 1;
                continue;//不用push,直接淘汰
            }
            //知足全部條件 push 
            ss.push(vals[i]);
        }
        return result;
    }
};

4. 如何實現Huffman編碼?

霍夫曼編碼不只會考察文本中有多少個不一樣字符,還會考察每一個字符出現的頻率,根據頻率的不一樣,選擇不一樣長度的編碼。霍夫曼編碼試圖用這種不等長的編碼方法,來進一步增長壓縮的效率。如何給不一樣頻率的字符選擇不一樣長度的編碼呢?根據貪心的思想,咱們能夠把出現頻率比較多的字符,用稍微短一些的編碼;出現頻率比較少的字符,用稍微長一些的編碼。

5. 在一個非負整數 a 中,咱們但願從中移除 k 個數字,讓剩下的數字值最小,如何選擇移除哪 k 個數字呢?

搞不懂他要幹個啥!!!!

6. 假設有 n 我的等待被服務,可是服務窗口只有一個,每一個人須要被服務的時間長度是不一樣的,如何安排被服務的前後順序,才能讓這 n 我的總的等待時間最短?

一直時間最短的先服務(堆)

感受看完這個應該和DP好好作個對比,認清兩種算法思想.
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息