數據結構——貪心算法

最近開始學習王爭老師的《數據結構與算法之美》,通過總結再加上自己的思考的形式記錄這門課程,文章主要作爲學習歷程的記錄。

首先來看一個例子,假設我們有一個可以容納100kg物品的揹包

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lcUW5zL1-1576140811915)(E:\數據結構(python)\images\貪心1.jpg)]

爲了使揹包中所裝物體的總價值最大,如何選擇在揹包中裝哪些豆子?這個問題的解決是將單價從高到低排列,它的本質藉助的是貪心算法。

總結一下貪心算法解決問題:

第一步,當我們看到這類問題時,首先要聯想到貪心算法。針對一組數據,我們定義了限制值和期望值,希望從中選出幾個數據,在滿足限制值的情況下,期望值最大。

第二步,我們嘗試看下這個問題是否可以用貪心算法解決。每次選擇當前情況下,在對限制值同等貢獻量的情況下,對期望值貢獻最大的數據。

第三步,我們舉幾個例子看下貪心算法產生的結果是否是最優的。大部分情況下,舉幾個例子驗證一下就可以了。

實際上,用貪心算法解決問題的思路,並不總能給出最優解。舉個例子,在一個有權圖中,從頂點S開始,找一條到頂點T的最短路徑(路徑中邊的權值和最小)。貪心算法的解決思路是,每次都選擇一條跟當前頂點相連的權最小的邊,直到找到頂點T。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-neDkbDPy-1576140811916)(E:\數據結構(python)\images\貪心2.jpg)]

按照這個思路,我們求出的最短路徑是S-A-E-T,路徑長度是1+4+4=9.

但這種貪心的選擇方式,最終求得路徑並不是最短路徑,因爲路徑S-B-D-T纔是最短路徑,因爲這條路徑的長度是2+2+2=6.在這個問題上,貪心算法不工作的主要原因是前面的選擇會影響後面的選擇。即便第一步選擇最優的走法,但有可能因爲這一步選擇,導致後面選擇都很糟糕。

貪心算法實戰分析

1、分糖果

我們有m個糖果和n個孩子,m<n。要將糖果分給孩子喫,每個糖果的大小不等,這m個糖果的大小分別是S1、S2、S3、…、Sm。除此之外,每個孩子對糖果大小的需求不一樣,只有糖果的大小大於等於孩子的對糖果大小的需求時,孩子纔得到滿足假設這n個孩子對糖果大小需求爲g1、g2、g3、…、gn,如何分配糖果才能滿足最多數量的孩子?

這個問題可以抽象成從n個孩子中,抽取一部分孩子分配糖果,讓滿足的孩子的個數最大。這個問題的限制值就是糖果個數m。

採用貪心算法,我們可以從需求小的孩子開始分配糖果。因爲滿足一個需求大的孩子跟滿足一個需求小的孩子,對期望值的貢獻是一樣的。我們每次從剩下的孩子中,找出對糖果大小需求最小的,然後發給他剩下的糖果中能滿足他的最小的糖果,這樣得到的分配方案,也就是滿足的孩子個數最多的方案。

以力扣455題爲例,假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。但是,每個孩子最多隻能給一塊餅乾。對每個孩子 i ,都有一個胃口值 gi ,這是能讓孩子們滿足胃口的餅乾的最小尺寸;並且每塊餅乾 j ,都有一個尺寸 sj 。如果 sj >= gi ,我們可以將這個餅乾 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是儘可能滿足越多數量的孩子,並輸出這個最大數值。(你可以假設胃口值爲正。一個小朋友最多隻能擁有一塊餅乾。)

class Solution(object):
    def findContentChildren(self, g, s):
        num = 0
        g = sorted(g)
        s = sorted(s)
        for ele in s:
            if num<len(g) and ele>=g[num]:
                num+=1
        return num
2、錢幣找零

假設我們有1元、2元、5元、10元、20元、50元、100元這些面額的紙幣,它們的張數分別是C1、C2、C5、C10、C20、C50、C100.現在要用這些錢來支付K元,最少要用多少張紙幣?

在貢獻相同期望值(紙幣數目)的情況下,我們希望多貢獻點金額。這樣可以讓紙幣數更少。

3、區間覆蓋

假設我們有 n 個區間,區間的起始端點和結束端點分別是 [l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。我們從這 n 個區間中選出一部分區間,這部分區間滿足兩兩不相交(端點相交的情況不算相交),最多能選出多少個區間呢?

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vUX3lODF-1576140811917)(E:\數據結構(python)\images\貪心3.jpg)]

這個問題的解決思路:假設這 n 個區間中最左端點是 lmin,最右端點是 rmax。這個問題就相當於,我們選擇幾個不相交的區間,從左到右將 [lmin, rmax] 覆蓋上。我們按照起始端點從小到大的順序對這 n 個區間排序。我們每次選擇的時候,左端點跟前面的已經覆蓋的區間不重合的,右端點又儘量小的,這樣可以讓剩下的未覆蓋區間儘可能的大,就可以放置更多的區間。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GCTRoYA4-1576140811918)(E:\數據結構(python)\images\貪心4.jpg)]

用貪心算法實現霍夫曼編碼

假設有1個包含1000個字符的文件,每個字符佔1個byte(1個byte=8bit),存儲這1000個字符就一共需要8000bits,需要一種更節省空間的存儲方式。

假設通過統計這1000個字符只包含6個不同的字符,分別爲a、b、c、d、e、f。若用3個二進制位(bit)就可以表示8個不同字符,此時用3000個bits即可。

這時候霍夫曼編碼出現了。霍夫曼編碼可以更加節省空間。它不僅會考察文本中有多少個字符,還會考察每個字符出現的頻率。根據頻率的不同,選擇不同長度的編碼。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-BLoBCzYF-1576140811918)(E:\數據結構(python)\images\貪心5.jpg)]

這時候,1000個字符只需要2100bits。

處理過程:我們把每個字符看作一個節點,並且輔帶着把頻率放到優先級隊列中。我們從隊列中取出頻率最小的兩個節點 A、B,然後新建一個節點 C,把頻率設置爲兩個節點的頻率之和,並把這個新節點 C 作爲節點 A、B 的父節點。最後再把 C 節點放入到優先級隊列中。重複這個過程,直到隊列中沒有數據。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vMzfChKw-1576140811918)(E:\數據結構(python)\images\貪心6.jpg)]

我們給每一條邊加上畫一個權值,指向左子節點的邊我們統統標記爲 0,指向右子節點的邊,我們統統標記爲 1,那從根節點到葉節點的路徑就是葉節點對應字符的霍夫曼編碼。

在這裏插入圖片描述

節點的路徑就是葉節點對應字符的霍夫曼編碼。

參考資料:王爭《數據結構與算法之美》

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