算法六——贪心
文章出處:極客時(shí)間《數(shù)據(jù)結(jié)構(gòu)和算法之美》-作者:王爭(zhēng)。該系列文章是本人的學(xué)習(xí)筆記。
1 背豆子的例子
假設(shè)我們有一個(gè)可以容納 100kg 物品的背包,可以裝各種物品。我們有以下 5 種豆子,每種豆子的總量和總價(jià)值都各不相同。為了讓背包中所裝物品的總價(jià)值最大,我們?nèi)绾芜x擇在背包中裝哪些豆子?每種豆子又該裝多少呢?
我們最直接的思路是算出每種豆子每kg的價(jià)值,按照價(jià)值從大到小排序。先裝價(jià)值高的。單價(jià)從高到低排列,依次是:黑豆、綠豆、紅豆、青豆、黃豆,所以,我們可以往背包里裝 20kg 黑豆、30kg 綠豆、50kg 紅豆。
2 貪心解決問(wèn)題的步驟
1 當(dāng)我們看到這類問(wèn)題的時(shí)候想到要用貪心:針對(duì)一組數(shù)據(jù),定義限制值和期望值,希望從中選擇幾個(gè)數(shù)據(jù),在滿足限制值的情況下,期望值最大。
例子中限制值是背包總?cè)萘?#xff0c;期望值是總價(jià)值最大。
2 我們嘗試看下這個(gè)問(wèn)題是否可以用貪心解決:每次選擇當(dāng)前情況下,在對(duì)限制值同等貢獻(xiàn)量的情況下,對(duì)期望值貢獻(xiàn)最大。
優(yōu)先選擇黑豆,在同樣裝1kg的時(shí)候,增加總價(jià)值最多。
3 我們舉幾個(gè)例子看下,用貪心得到的結(jié)果是不是最優(yōu)的。大部分情況下,舉幾個(gè)例子就可以,貪心的證明是非常復(fù)雜的。
3 貪心實(shí)戰(zhàn)例子
3.1 分糖果
有m個(gè)糖果,n個(gè)孩子。我們要把這個(gè)糖果分給孩子,一個(gè)孩子最多只能有一個(gè)糖果。糖果數(shù)量小于孩子數(shù)量。
每個(gè)糖果的大小也不同,這m個(gè)糖果的大小分別為s1、s2…sm。每個(gè)孩子對(duì)糖果的需求量不同,分別為t1、t2…tn。只有糖果大小大于等于孩子需求量的時(shí)候,孩子才能滿足。
怎么分配才能讓滿足更多的孩子。
限制值是:糖果的數(shù)量。期望值是:獲得滿足的孩子的個(gè)數(shù)越多越好。
我們嘗試用貪心解決。先滿足需求量低的孩子。因?yàn)樾枨罅康偷暮⒆痈菀诐M足,而且無(wú)論孩子需求量大小,滿足一個(gè),就可以讓期望值加1。糖果按照從小到大排序,在滿足當(dāng)前孩子需求量的范圍內(nèi)選擇最小的糖果。更大的糖果應(yīng)該用來(lái)滿足需求量更大的孩子。糖果無(wú)論大小,分配一個(gè),就是給限制值加1。
所以,分配方案是:孩子按照需求量從小到大排序,糖果選擇滿足需求量的最小的糖果。可以滿足更多的孩子。
3.2 錢幣找零
當(dāng)給別人找零的時(shí)候,我們希望找零的錢的數(shù)量越少越好。當(dāng)然,錢的總量是要足夠的。例如找25元,一張20+一張5元是最好的選擇。
3.3 區(qū)間覆蓋
假設(shè)我們有 n 個(gè)區(qū)間,區(qū)間的起始端點(diǎn)和結(jié)束端點(diǎn)分別是 [l1, r1],[l2, r2],[l3, r3],……,[ln, rn]。我們從這 n 個(gè)區(qū)間中選出一部分區(qū)間,這部分區(qū)間滿足兩兩不相交(端點(diǎn)相交的情況不算相交),最多能選出多少個(gè)區(qū)間呢?
我們假設(shè)最左端是lmin,最右端是rmax。這就要求在[lmin,rmax]區(qū)間上找到數(shù)量更多的不重合的線段。我們按照左邊端點(diǎn)從小到大排序這幾個(gè)區(qū)間。每次選擇的時(shí)候左邊端點(diǎn)和前面已經(jīng)覆蓋的區(qū)間不重合,右邊端點(diǎn)又盡可能的小。這樣可以讓剩下的未覆蓋的區(qū)間更大,未來(lái)放置更多的區(qū)間段。
4 霍夫曼壓縮編碼
假設(shè)一篇文章有1000個(gè)字符,每個(gè)字符1個(gè)字節(jié)=8位。總共需要8000個(gè)bits。
如果這1000個(gè)字符只包含6種字符,分別為a、b、c、d、e、f。只需要3位就可以表示這6個(gè)字符。總共需要3000個(gè)bits。比最開(kāi)始空間減少了。
a(000)、b(001)、c(010)、d(011)、e(100)、f(101)是否可以再壓縮呢?使用霍夫曼編碼。
霍夫曼編碼不僅考察文件中有多少種不同的字符,還統(tǒng)計(jì)不同字符的出現(xiàn)頻率。根據(jù)頻率不同,選擇不同長(zhǎng)度的編碼。根據(jù)貪心思想,字符頻率越高,使用的編碼稍短;字符頻率低,使用的編碼稍長(zhǎng)。當(dāng)編碼不等長(zhǎng)的時(shí)候,怎么從壓縮文件中讀字符呢?這個(gè)時(shí)候我們要求一個(gè)字符不能是另外一個(gè)字符的前綴。
假設(shè)這 6 個(gè)字符出現(xiàn)的頻率從高到低依次是 a、b、c、d、e、f。我們把它們編碼下面這個(gè)樣子,任何一個(gè)字符的編碼都不是另一個(gè)的前綴,在解壓縮的時(shí)候,我們每次會(huì)讀取盡可能長(zhǎng)的可解壓的二進(jìn)制串,所以在解壓縮的時(shí)候也不會(huì)歧義。經(jīng)過(guò)這種編碼壓縮之后,這 1000 個(gè)字符只需要 2100bits 就可以了。
4.1 設(shè)計(jì)霍夫曼樹
4.1.1 插入隊(duì)列,形成一棵樹
如何設(shè)計(jì)字符編碼呢?按照出現(xiàn)頻率由低到高排序。我們把每個(gè)字符看作一個(gè)節(jié)點(diǎn),并且付帶著把頻率放到優(yōu)先級(jí)隊(duì)列中。我們從隊(duì)列中取出頻率最小的兩個(gè)節(jié)點(diǎn) A、B,然后新建一個(gè)節(jié)點(diǎn) C,把頻率設(shè)置為兩個(gè)節(jié)點(diǎn)的頻率之和,并把這個(gè)新節(jié)點(diǎn) C 作為節(jié)點(diǎn) A、B 的父節(jié)點(diǎn)。最后再把 C 節(jié)點(diǎn)放入到優(yōu)先級(jí)隊(duì)列中。重復(fù)這個(gè)過(guò)程,直到隊(duì)列中沒(méi)有數(shù)據(jù)。
4.2 編碼
現(xiàn)在,我們給每一條邊加上畫一個(gè)權(quán)值,指向左子節(jié)點(diǎn)的邊我們統(tǒng)統(tǒng)標(biāo)記為 0,指向右子節(jié)點(diǎn)的邊,我們統(tǒng)統(tǒng)標(biāo)記為 1,那從根節(jié)點(diǎn)到葉節(jié)點(diǎn)的路徑就是葉節(jié)點(diǎn)對(duì)應(yīng)字符的霍夫曼編碼。
總結(jié)
- 上一篇: 689 Maximum Sum of
- 下一篇: 五大常用算法