python編程導論_第十六課

學習安排(8月27日-8月28日)
1.主要學習視頻Week9&期末考試
鏈接(http://www.xuetangx.com/courses/MITx/6_00_2x/2014_T2/courseware/d39541ec36564a88af34d319a2f16bd7/
2.參考書第13章動態(tài)規(guī)劃

動態(tài)規(guī)劃是一種非常高效的方法,適用于解決具有重復子問題和最優(yōu)子結(jié)構(gòu)的問題。

  • 如果一個問題的全局最優(yōu)解可以通過組合局部子問題的最優(yōu)解求出顷锰,那么這個問題就具有最優(yōu)子結(jié)構(gòu)。我們已經(jīng)見過一些這樣的問題嚼蚀,比如歸并排序。歸并排序?qū)σ粋€列表進行排序的方式就是先對子列表進行排序,然后再合并子列表的排序結(jié)果。
  • 如果求出一個問題的最優(yōu)解時需要對同樣的某個問題求解多次拾酝,那么這個問題就具有重疊子
    問題。

0/1背包問題具有這兩個特性卡者,盡管不太明顯蒿囤。我們要先看一個更明顯具有最優(yōu)子結(jié)構(gòu)和重疊子問題的問題。

又見斐波那契數(shù)列

之前我們介紹了一個很直觀的斐波那契數(shù)列的遞歸實現(xiàn):

def fib(n):
"""假設n是非負整數(shù)
返回第n個斐波那契數(shù)"""
    if n == 0 or n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

雖然這個遞歸實現(xiàn)是正確的虎眨,但效率太差蟋软。復雜度的增長與函數(shù)結(jié)果的增長成正比镶摘,而斐波那契數(shù)列的增長速度非乘宰快。舉例來說凄敢,fib(120)是8 670 007 398 507 948 658 051 921碌冶。如果每次遞歸調(diào)用需要1納秒,那么fib(120)需要250 000年才能結(jié)束涝缝。fib函數(shù)只有寥寥幾行代碼扑庞,很明顯問題出在fib函數(shù)調(diào)用自己的次數(shù)上譬重。舉個例子,我們看一下fib(6)的調(diào)用樹:


遞歸形式的斐波那契調(diào)用樹

請注意罐氨,我們在一遍又一遍地計算同一個值臀规。例如,fib(3)被調(diào)用了3次栅隐,而且每一次調(diào)用又引發(fā)了對fib函數(shù)的另外4次調(diào)用塔嬉。很容易就能想到,可以將fib函數(shù)的第一次調(diào)用結(jié)果保存下來租悄,然后在需要的時候直接查找谨究,而不是重新計算。這種方法稱為備忘錄法泣棋,是動態(tài)規(guī)劃的核心思想胶哲。

下面給出了一個基于備忘錄法的斐波那契函數(shù)的具體實現(xiàn)。函數(shù)fastFib中有一個參數(shù)memo潭辈,用來記錄已經(jīng)計算過的函數(shù)值鸯屿,這個參數(shù)的默認值是一個空字典。當使用一個大于1的整數(shù)n調(diào)用fastFib時把敢,fastFib會先在memo中尋找n碾盟,如果沒有找到(因為這時是第一次使用這個值調(diào)用fastFib),就會拋出一個異常技竟。此時冰肴,fastFib就使用標準的斐波那契遞推公式,并將結(jié)果保存在memo中榔组。

def fastFib(n, memo = {}):
    """假設n是非負整數(shù)熙尉,memo只進行遞歸調(diào)用 返回第n個斐波那契數(shù)"""
    if n == 0 or n == 1:
        return 1
    try:
        return memo[n]
    except KeyError:
        result = fastFib(n-1, memo) + fastFib(n-2, memo)
        memo[n] = result
        return result

動態(tài)規(guī)劃與0/1 背包問題

我們介紹過一種最優(yōu)化問題,即0/1背包問題搓扯〖焯担回憶一下,我們介紹了一種復雜度為O(nlog(n))的貪婪算法锨推,但這種算法不能保證找到最優(yōu)解铅歼。除此之外,我們還介紹了一種可以保證找到最優(yōu)解的暴力算法换可,但運行時間是指數(shù)增長的椎椰。動態(tài)規(guī)劃可以提供一種實用的方法,在合理的時間內(nèi)解決大部分0/1背包問題沾鳄。作為推導解決方案的第一步慨飘,我們先基于窮舉法得到一個指數(shù)級別的解法。核心思想就是構(gòu)造一個根二叉樹,枚舉所有滿足重量約束的狀態(tài)瓤的,從而探索可行解空間休弃。

在0/1背包問題的搜索樹中,每個節(jié)點都使用一個四元組進行標注圈膏,這個四元組表示的是這種背包問題的一個局部解塔猾。四元組中的四個元素如下:

  • 要帶走的物品集合;
  • 還沒有決定是否要帶走的物品列表稽坤;
  • 要帶走的物品集合中的物品總價值(這個值只是為了優(yōu)化算法桥帆,因為可以從集合中計算出這個值);
  • 背包的剩余空間(這也同樣是一種算法優(yōu)化方式慎皱,因為這個值可以通過背包允許的總重量減去當前要帶走的物品總重量計算出來)老虫。

這個樹是從根節(jié)點開始,自頂向下地構(gòu)建出來的茫多。我們從待定物品中選擇出一個祈匙,如果背包放得下這個物品,就建立一個節(jié)點天揖,反映出選擇帶走這個物品的后果夺欲。按照慣例,我們將這個節(jié)點作為左子節(jié)點今膊,而用右子節(jié)點表示不帶走這個物品的后果些阅。以遞歸方式不斷執(zhí)行這個過程,直到背包被裝滿或者沒有待定物品斑唬。因為每條邊都表示一個決策(帶走或不帶走某個物品)市埋,所以這種樹稱為決策樹。

如下是一個表示物品集合的表格恕刘。

名稱 重量
a 6 3
b 7 3
c 8 2
d 9 5

給出一個決策樹缤谎,在背包能夠容納的最大重量為5的假設之下,可以確定應該帶走哪些物品褐着。樹的根節(jié)點(節(jié)點0)有一個標簽<{}, [a, b, c, d], 0, 5>坷澡,表示沒有選擇物品,所有物品都處于待定狀態(tài)含蓉,帶走的物品總值為0频敛,背包剩余空間還能容納的重量為5。節(jié)點1表示物品a被帶走馅扣,物品[b, c, d]處于待定狀態(tài)斟赚,帶走的物品總值為6,背包還能容納2的重量岂嗓。節(jié)點1沒有左子節(jié)點汁展,因為物品b的重量為3,不能放在背包中厌殉。


背包問題的決策樹

對于每個葉節(jié)點食绿,或者第二個元素為空列表(表示沒有物品可以考慮是否帶走),或者第四個元素為0(表示背包中已經(jīng)沒有剩余空間)公罕。這種深度優(yōu)先的樹搜索使用遞歸實現(xiàn)如下器紧。

def maxVal(toConsider, avail):
"""假設toConsider是一個物品列表,avail表示重量
返回一個元組表示0/1背包問題的解楼眷,包括物品總價值和物品列表"""
    if toConsider == [] or avail == 0:
        result = (0, ())
    elif toConsider[0].getWeight() > avail:
        #探索右側(cè)分支
        result = maxVal(toConsider[1:], avail)
    else:
        nextItem = toConsider[0]
        #探索左側(cè)分支
        withVal, withToTake = maxVal(toConsider[1:],
        avail - nextItem.getWeight())
        withVal += nextItem.getValue()
        #探索右側(cè)分支
        withoutVal, withoutToTake = maxVal(toConsider[1:], avail)
        #選擇更好的分支
        if withVal > withoutVal:
            result = (withVal, withToTake + (nextItem,))
        else:
            result = (withoutVal, withoutToTake)
    return result

程序中是否還有重疊子問題呢铲汪?乍一看似乎沒有。在樹的每一層罐柳,我們考慮的都是不同的可用物品集合掌腰,這說明如果確實存在普通的重疊子問題,那么它們一定在樹的同一層张吉。實際上齿梁,同一層的每個節(jié)點的待定物品集合確實是一樣的。不過肮蛹,從圖13-4中的標注可以看出勺择,某層的每一個節(jié)點的待定物品集合和更高層中節(jié)點的待定物品集合是不同的。

考慮一下每個節(jié)點需要解決的問題伦忠。這個的問題就是省核,在給定剩余可用重量的情況下,從待定物品集中找到一個最優(yōu)的物品昆码。決定剩余可用重量的不是帶走的具體物品或帶走的物品的總價值气忠,而是帶走的物品的總重量。所以赋咽,舉例來說笔刹,在決策樹圖中,節(jié)點2和節(jié)點7要解決的實際上是同一個問題:在給定剩余可用重量為2的情況下冬耿,確定待定物品集合[c, d]中應該帶走哪個物品舌菜。

下面的代碼利用最優(yōu)子結(jié)構(gòu)和重疊子問題,為0/1背包問題提供了一個動態(tài)規(guī)劃解決方案亦镶。通過添加一個附加參數(shù)memo日月,記錄已經(jīng)解決的子問題的解。memo是使用字典實現(xiàn)的缤骨,它的鍵由toConsider的長度和剩余可用重量構(gòu)成爱咬。表達式len(toConsider)是待定物品集合的一種簡潔表示,可以這樣表示的原因是物品總是從列表toConsider的同一端(前端)被移除绊起。

def fastMaxVal(toConsider, avail, memo = {}):
    """假設toConsider是物品列表精拟,avail表示重量memo進行遞歸調(diào)用
    返回一個元組表示0/1背包問題的解,包括物品總價值和物品列表"""
    if (len(toConsider), avail) in memo:
        result = memo[(len(toConsider), avail)]
    elif toConsider == [] or avail == 0:
        result = (0, ())
    elif toConsider[0].getWeight() > avail:
        #探索右側(cè)分支
        result = fastMaxVal(toConsider[1:], avail, memo)
    else:
        nextItem = toConsider[0]
        #探索左側(cè)分支
        withVal, withToTake =\
        fastMaxVal(toConsider[1:],
        avail - nextItem.getWeight(), memo)
        withVal += nextItem.getValue()
        #探索右側(cè)分支
        withoutVal, withoutToTake = fastMaxVal(toConsider[1:],
        avail, memo)
        #選擇更好的分支
        if withVal > withoutVal:
            result = (withVal, withToTake + (nextItem,))
        else:
            result = (withoutVal, withoutToTake)
    memo[(len(toConsider), avail)] = result
    return result
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蜂绎,隨后出現(xiàn)的幾起案子栅表,更是在濱河造成了極大的恐慌,老刑警劉巖师枣,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怪瓶,死亡現(xiàn)場離奇詭異,居然都是意外死亡践美,警方通過查閱死者的電腦和手機洗贰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陨倡,“玉大人敛滋,你說我怎么就攤上這事⌒烁铮” “怎么了绎晃?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長帖旨。 經(jīng)常有香客問我箕昭,道長,這世上最難降的妖魔是什么解阅? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任落竹,我火速辦了婚禮,結(jié)果婚禮上货抄,老公的妹妹穿的比我還像新娘述召。我一直安慰自己,他們只是感情好蟹地,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布积暖。 她就那樣靜靜地躺著,像睡著了一般怪与。 火紅的嫁衣襯著肌膚如雪夺刑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天分别,我揣著相機與錄音遍愿,去河邊找鬼。 笑死耘斩,一個胖子當著我的面吹牛沼填,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播括授,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼坞笙,長吁一口氣:“原來是場噩夢啊……” “哼岩饼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起薛夜,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤籍茧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后却邓,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體硕糊,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡院水,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年腊徙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檬某。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡撬腾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恢恼,到底是詐尸還是另有隱情民傻,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布场斑,位于F島的核電站漓踢,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏漏隐。R本人自食惡果不足惜喧半,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望青责。 院中可真熱鬧挺据,春花似錦、人聲如沸脖隶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽产阱。三九已至婉称,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間构蹬,已是汗流浹背王暗。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怎燥,地道東北人瘫筐。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像铐姚,于是被迫代替她去往敵國和親策肝。 傳聞我的和親對象是個殘疾皇子肛捍,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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

  • 學習安排(8月16日-8月20日)1.主要學習視頻Week6鏈接(http://www.xuetangx.com/...
    fourup閱讀 759評論 0 0
  • 先是原文復制: P01: 01背包問題題目有N件物品和一個容量為V的背包。第i件物品的費用是c[i]之众,價值是w[i...
    Buyun0閱讀 475評論 0 0
  • 操作系統(tǒng) 線程和進程的區(qū)別(1)地址空間:進程內(nèi)的一個執(zhí)行單元;進程至少有一個線程;它們共享進程的地址空間;而進程...
    Stephen__Li閱讀 596評論 0 1
  • 對不起親愛的拙毫,我要和你作別了 親愛的對不起,今天我有好多話話想對你說棺禾,我想對你做一個決定缀蹄,做一次懺悔。 首先感謝你...
    2608閱讀 371評論 0 0
  • 人生逆境時膘婶、切記忍耐缺前!人生順境時、切記收斂
    SAewhalesong閱讀 190評論 0 0