貪心算法

概述

貪心算法(英語:greedy algorithm)茫多,又稱貪婪算法盲镶,是一種在每一步選擇中都采取在當(dāng)前狀態(tài)下最好或最優(yōu)(即最有利)的選擇,從而希望導(dǎo)致結(jié)果是最好或最優(yōu)的算法

貪心算法解決問題的正確性雖然很多時(shí)候都看起來是顯而易見的仰剿,但是要嚴(yán)謹(jǐn)?shù)刈C明算法能夠得到最優(yōu)解全陨,并不是件容易的事。所以甚颂,很多時(shí)候蜜猾,我們只需要多舉幾個(gè)例子,看一下貪心算法的解決方案是否真的能得到最優(yōu)解就可以了振诬。

適用情況

貪心法可以解決一些最優(yōu)化問題蹭睡,如:求中的最小生成樹、求哈夫曼編碼……對(duì)于其他問題赶么,貪心法一般不能得到我們所要求的答案肩豁。一旦一個(gè)問題可以通過貪心法來解決,那么貪心法一般是解決這個(gè)問題的最好辦法辫呻。由于貪心法的高效性以及其所求得的答案比較接近最優(yōu)結(jié)果清钥,貪心法也可以用作輔助算法或者直接解決一些要求結(jié)果不特別精確的問題。

貪心算法的2個(gè)條件:

最優(yōu)子結(jié)構(gòu)性質(zhì)
如果一個(gè)問題的最優(yōu)解包含其子問題的最優(yōu)解放闺,我們就稱此問題具有最優(yōu)子結(jié)構(gòu)性質(zhì)循捺。
貪心選擇性質(zhì)
對(duì)于某個(gè)問題,如果我們可以通過做出局部最優(yōu)(貪心)選擇來構(gòu)造全局最優(yōu)解雄人,那么我們就稱該問題具有貪心選擇性質(zhì)。

基本步驟

第一步,當(dāng)我們看到這類問題的時(shí)候础钠,首先要聯(lián)想到貪心算法:針對(duì)一組數(shù)據(jù)恰力,我們定義了限制值和期望值,希望從中選出幾個(gè)數(shù)據(jù)旗吁,在滿足限制值的情況下踩萎,期望值最大。

第二步很钓,我們嘗試看下這個(gè)問題是否可以用貪心算法解決:每次選擇當(dāng)前情況下香府,在對(duì)限制值同等貢獻(xiàn)量的情況下,對(duì)期望值貢獻(xiàn)最大的數(shù)據(jù)码倦。

第三步企孩,我們舉幾個(gè)例子看下貪心算法產(chǎn)生的結(jié)果是否是最優(yōu)的。
大部分情況下袁稽,舉幾個(gè)例子驗(yàn)證一下就可以了勿璃。嚴(yán)格地證明貪心算法的正確性,是非常復(fù)雜的推汽,需要涉及比較多的數(shù)學(xué)推理补疑。而且,從實(shí)踐的角度來說歹撒,大部分能用貪心算法解決的問題莲组,貪心算法的正確性都是顯而易見的,也不需要嚴(yán)格的數(shù)學(xué)推導(dǎo)證明暖夭。

實(shí)戰(zhàn)分析

1 分糖果

我們有 m 個(gè)糖果和 n 個(gè)孩子锹杈。我們現(xiàn)在要把糖果分給這些孩子吃,但是糖果少鳞尔,孩子多(m<n)嬉橙,所以糖果只能分配給一部分孩子。 每個(gè)糖果的大小不等寥假,這 m 個(gè)糖果的大小分別是 s1市框,s2,s3糕韧,……枫振,sm。除此之外萤彩,每個(gè)孩子對(duì)糖果大小的需求也是不一樣的粪滤,只有糖果的大小大于等于孩子的對(duì)糖果大小的需求的時(shí)候,孩子才得到滿足雀扶。假設(shè)這 n 個(gè)孩子對(duì)糖果大小的需求分別是 g1杖小,g2肆汹,g3,……予权,gn昂勉。
我的問題是,如何分配糖果扫腺,能盡可能滿足最多數(shù)量的孩子岗照?

問題抽象:從 n 個(gè)孩子中,抽取一部分孩子分配糖果笆环,讓滿足的孩子的個(gè)數(shù)(期望值)是最大的攒至。這個(gè)問題的限制值就是糖果個(gè)數(shù) m。
使用貪心算法來解決:

  1. 對(duì)于一個(gè)孩子來說躁劣,如果小的糖果可以滿足迫吐,我們就沒必要用更大的糖果。
  2. 另一方面习绢,對(duì)糖果大小需求小的孩子更容易被滿足渠抹,所以,我們可以從需求小的孩子開始分配糖果闪萄。
  3. 因?yàn)闈M足一個(gè)需求大的孩子跟滿足一個(gè)需求小的孩子梧却,對(duì)我們期望值的貢獻(xiàn)是一樣的。
  4. 我們每次從剩下的孩子中败去,找出對(duì)糖果大小需求最小的放航,然后發(fā)給他剩下的糖果中能滿足他的最小的糖果,這樣得到的分配方案圆裕,也就是滿足的孩子個(gè)數(shù)最多的方案广鳍。
2 區(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ū)間呢竭缝?
類似場景任務(wù)調(diào)度、教師排課

image.png

image.png

問題抽象:

  1. 從n個(gè)區(qū)間中沼瘫,滿足兩兩不相交
  2. 區(qū)間的個(gè)數(shù)(期望值)是最大的抬纸。

使用貪心算法來解決:

  1. 將所有區(qū)間從左至右排列
  2. 子問題:讓達(dá)到一個(gè)期望(得到一個(gè)不相交區(qū)間)時(shí),rx所在位置盡可能靠左(因?yàn)楹罄m(xù)區(qū)間越容易滿足不相交)耿戚。則rx 即為當(dāng)前期望下rx的最優(yōu)解湿故。
3 霍夫曼編碼

假設(shè)我有一個(gè)包含 1000 個(gè)字符的文件阿趁,每個(gè)字符占 1 個(gè) byte(1byte=8bits),?存儲(chǔ)這 1000 個(gè)字符就一共需要 8000bits晓锻,那有沒有更加節(jié)省空間的存儲(chǔ)方式呢歌焦?

問題抽象:

  1. 總的字符加權(quán)和最小(字符*頻率加和)

使用貪心算法來解決:

  1. 頻率高的占用字符盡可能少
    image.png

    霍夫曼樹動(dòng)態(tài)創(chuàng)建過程:
    霍夫曼樹動(dòng)態(tài)創(chuàng)建過程演示

定義霍夫曼樹結(jié)構(gòu)

package huffman

import (
    "container/heap"
    "fmt"
)

type HuffmanTree interface {
    Freq() int //頻率
}

type HuffmanLeaf struct {
    freq  int
    value rune
}

type HuffmanNode struct {
    freq        int
    left, right HuffmanTree
}

func (self HuffmanLeaf) Freq() int {
    return self.freq
}

func (self HuffmanNode) Freq() int {
    return self.freq
}


實(shí)現(xiàn)優(yōu)先隊(duì)列砚哆,使用container/heap 包


type treeHeap []HuffmanTree

func (th treeHeap) Len() int { return len(th) }
func (th treeHeap) Less(i, j int) bool {
    return th[i].Freq() < th[j].Freq()
}
func (th *treeHeap) Push(ele interface{}) {
    *th = append(*th, ele.(HuffmanTree))
}
func (th *treeHeap) Pop() (popped interface{}) {
    popped = (*th)[len(*th)-1]
    *th = (*th)[:len(*th)-1]
    return
}
func (th treeHeap) Swap(i, j int) { th[i], th[j] = th[j], th[i] }

構(gòu)建霍夫曼樹

func buildTree(symFreqs map[rune]int) HuffmanTree {
    var trees treeHeap
    for c, f := range symFreqs {
        trees = append(trees, HuffmanLeaf{f, c})
    }
    heap.Init(&trees)
    for trees.Len() > 1 {
        a := heap.Pop(&trees).(HuffmanTree)
        b := heap.Pop(&trees).(HuffmanTree)

        heap.Push(&trees, HuffmanNode{a.Freq() + b.Freq(), a, b})
    }
    return heap.Pop(&trees).(HuffmanTree)
}

輸出霍夫曼編碼表

func printCodes(tree HuffmanTree, prefix []byte) {
    switch i := tree.(type) {
    case HuffmanLeaf:
        fmt.Printf("%c\t%d\t%s\n", i.value, i.freq, string(prefix))
    case HuffmanNode:
        prefix = append(prefix, '0')
        printCodes(i.left, prefix)
        prefix = prefix[:len(prefix)-1]

        prefix = append(prefix, '1')
        printCodes(i.right, prefix)
        prefix = prefix[:len(prefix)-1]
    }
}

運(yùn)行測試

func TestHuff(t *testing.T) {
    test := "aaaaabbbbcccdd"

    symFreqs := make(map[rune]int)
    for _, c := range test {
        symFreqs[c]++
    }

    // example tree
    exampleTree := buildTree(symFreqs)

    // print out results
    fmt.Println("SYMBOL\tWEIGHT\tHUFFMAN CODE")
    printCodes(exampleTree, []byte{})
}

輸出結(jié)果:

=== RUN   TestHuff
SYMBOL  WEIGHT  HUFFMAN CODE
a   5   0
b   4   10
d   2   110
c   3   111
--- PASS: TestHuff (0.00s)
PASS
4 N位數(shù)刪除K個(gè)數(shù)字,使剩下的數(shù)字串最小

使用貪心算法來解決:

  1. 分解為每次刪除1位數(shù)字屑墨,刪除k次躁锁,每次都要求刪除后的數(shù)字串最小
  2. 刪除出現(xiàn)的第一個(gè)左邊>右邊的數(shù),刪除之后高位減小卵史,因?yàn)榱粝碌臄?shù)總是當(dāng)前最優(yōu)解
  3. 執(zhí)行k次战转,得到全局最優(yōu)解

//刪除k個(gè)字符,使保留的的數(shù)字串最小
func solutionNK(number int, k int) string {
    numberStr := strconv.Itoa(number)
    numbers := []rune(numberStr)
    oriLen := len(numbers)
    flag := true
    //刪除k次以躯,每次刪除第一次逆序的數(shù)字
    for i := 0; i < k; i++ {
        flag = true
        for i := 0; i < len(numbers)-1; i++ {
            num1, _ := strconv.Atoi(string(numbers[i]))
            num2, _ := strconv.Atoi(string(numbers[i+1]))
            if num1 > num2 {
                //刪除i
                numbers = append(numbers[:i], numbers[i+1:]...)
                flag = false
                break
            }
        }
        if flag {
            break
        }
    }
    //如果所有數(shù)字遞增槐秧,則刪除最后幾個(gè)數(shù)字直接返回
    numbers = numbers[:oriLen-k]
    return string(numbers)
}

貪心算法的證明

https://blog.csdn.net/weixin_34342207/article/details/86877529

反例:

1 路徑規(guī)劃問題
image.png

但是,這種貪心的選擇方式忧设,最終求的路徑并不是最短路徑刁标,因?yàn)槁窂?S->B->D->T 才是最短路徑。
為什么貪心算法在這個(gè)問題上不工作了呢址晕?
前面的選擇膀懈,會(huì)影響后面的選擇。
如果我們第一步從頂點(diǎn) S 走到頂點(diǎn) A谨垃,那接下來面對(duì)的頂點(diǎn)和邊启搂,跟第一步從頂點(diǎn) S 走到頂點(diǎn) B,是完全不同的刘陶。所以胳赌,即便我們第一步選擇最優(yōu)的走法(邊最短),但有可能因?yàn)檫@一步選擇匙隔,導(dǎo)致后面每一步的選擇都很糟糕疑苫,最終也就無緣全局最優(yōu)解了。

2 錢幣找零

這個(gè)問題在我們的日常生活中更加普遍牡直。假設(shè)我們有 1 元缀匕、2 元、5 元碰逸、10 元乡小、20 元、50 元饵史、100 元這些面額的紙幣满钟,它們的張數(shù)分別是 c1胜榔、c2、c5湃番、c10夭织、c20、c50吠撮、c100尊惰。我們現(xiàn)在要用這些錢來支付 K 元,最少要用多少張紙幣呢泥兰?

找零問題是不是都可以用貪心算法弄屡?

反例:考慮幣值為100,99和1的幣種鞋诗,每種各一百張膀捷,找396元。動(dòng)態(tài)規(guī)劃可求出四張99元削彬,但貪心算法解出需三張一百和96張一元全庸。

怎樣的幣值可以用貪心算法進(jìn)行找零

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市融痛,隨后出現(xiàn)的幾起案子壶笼,更是在濱河造成了極大的恐慌,老刑警劉巖酌心,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拌消,死亡現(xiàn)場離奇詭異,居然都是意外死亡安券,警方通過查閱死者的電腦和手機(jī)墩崩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侯勉,“玉大人鹦筹,你說我怎么就攤上這事≈访玻” “怎么了铐拐?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長练对。 經(jīng)常有香客問我遍蟋,道長,這世上最難降的妖魔是什么螟凭? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任虚青,我火速辦了婚禮,結(jié)果婚禮上螺男,老公的妹妹穿的比我還像新娘棒厘。我一直安慰自己纵穿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布奢人。 她就那樣靜靜地躺著谓媒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪何乎。 梳的紋絲不亂的頭發(fā)上句惯,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音支救,去河邊找鬼宗弯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛搂妻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辕棚,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼欲主,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了逝嚎?” 一聲冷哼從身側(cè)響起扁瓢,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎补君,沒想到半個(gè)月后引几,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挽铁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年伟桅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叽掘。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡楣铁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出更扁,到底是詐尸還是另有隱情盖腕,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布浓镜,位于F島的核電站溃列,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏膛薛。R本人自食惡果不足惜听隐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望相叁。 院中可真熱鬧遵绰,春花似錦辽幌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至成玫,卻和暖如春加酵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背哭当。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工猪腕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钦勘。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓陋葡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親彻采。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腐缤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 貪心算法解決問題的步驟 第一步,當(dāng)我們看到這類問題的時(shí)候肛响,首先要聯(lián)想到貪心算法:針對(duì)一組數(shù)據(jù)岭粤,我們定義了限制值和期...
    liyoucheng2014閱讀 1,294評(píng)論 0 2
  • 貪心算法解決問題的步驟 當(dāng)我們看到這類問題的時(shí)候,首先要聯(lián)想到貪心算法:針對(duì)一組數(shù)據(jù)特笋,我們定義了它的限制值和期望值...
    wean_a23e閱讀 844評(píng)論 1 0
  • 概述 在前文中解釋了動(dòng)態(tài)規(guī)劃的基本思想剃浇,動(dòng)態(tài)規(guī)劃通過將一個(gè)問題劃分為規(guī)模更小的有限個(gè)子問題進(jìn)行求解,一般用于求解最...
    CodingTech閱讀 2,672評(píng)論 0 10
  • 動(dòng)態(tài)規(guī)劃和貪心算法都是用來求最優(yōu)化問題猎物,且二者都必須具有最有子結(jié)構(gòu)虎囚。貪心算法可以解決的問題,動(dòng)態(tài)規(guī)劃都能解決霸奕,可以...
    sereny閱讀 6,596評(píng)論 0 7
  • 目錄 1.貪心算法步驟 2.兩個(gè)關(guān)鍵要素 3.兩種背包問題3.1 0-1背包問題(適用于動(dòng)態(tài)規(guī)劃溜宽,不滿足貪心選擇性...
    王偵閱讀 4,913評(píng)論 2 3