貪心算法有不少經典的應用,好比霍夫曼編碼(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
老師想給孩子們分發糖果,有 N 個孩子站成了一條直線,老師會根據每一個孩子的表現,預先給他們評分。
你須要按照如下要求,幫助老師給這些孩子分發糖果:
每一個孩子至少分配到 1 個糖果。
相鄰的孩子中,評分高的孩子必須得到更多的糖果。
那麼這樣下來,老師至少須要準備多少顆糖果呢?
示例 1:
輸入: [1,0,2]
輸出: 5
解釋: 你能夠分別給這三個孩子分發 二、一、2 顆糖果。
示例 2:
輸入: [1,2,2]
輸出: 4
解釋: 你能夠分別給這三個孩子分發 一、二、1 顆糖果。
第三個孩子只獲得 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; } };
這個問題在咱們的平常生活中更加廣泛。假設咱們有 1 元、2 元、5 元、10 元、20 元、50 元、100 元這些面額的紙幣,它們的張數分別是 c一、c二、c五、c十、c20、c50、c100。咱們如今要用這些錢來支付 K 元,最少要用多少張紙幣呢?
在生活中,咱們確定是先用面值最大的來支付,若是不夠,就繼續用更小一點面值的,以此類推,最後剩下的用 1 元來補齊。
在貢獻相同指望值(紙幣數目)的狀況下,咱們但願多貢獻點金額,這樣就可讓紙幣數更少,這就是一種貪心算法的解決思路。直覺告訴咱們,這種處理方法就是最好的。實際上,要嚴謹地證實這種貪心算法的正確性,須要比較複雜的、有技巧的數學推導,我不建議你花太多時間在上面,不過若是感興趣的話,能夠本身去研究下。
假設咱們有 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; } };
霍夫曼編碼不只會考察文本中有多少個不一樣字符,還會考察每一個字符出現的頻率,根據頻率的不一樣,選擇不一樣長度的編碼。霍夫曼編碼試圖用這種不等長的編碼方法,來進一步增長壓縮的效率。如何給不一樣頻率的字符選擇不一樣長度的編碼呢?根據貪心的思想,咱們能夠把出現頻率比較多的字符,用稍微短一些的編碼;出現頻率比較少的字符,用稍微長一些的編碼。
搞不懂他要幹個啥!!!!
一直時間最短的先服務(堆)