動(dòng)態(tài)規(guī)劃

狀態(tài)轉(zhuǎn)移方程

從之前某個(gè)階段的某個(gè)或某些狀態(tài)狀態(tài)到下一個(gè)狀態(tài)之間的關(guān)系式钱贯,就叫做狀態(tài)轉(zhuǎn)移方程煤惩。

最優(yōu)子結(jié)構(gòu)

每個(gè)階段的最優(yōu)狀態(tài)可以從之前某個(gè)階段的某個(gè)或某些狀態(tài)直接得到的性質(zhì)叫做最優(yōu)子結(jié)構(gòu)毙籽。
最優(yōu)子結(jié)構(gòu)的產(chǎn)生往往是對(duì)狀態(tài)轉(zhuǎn)換方程結(jié)果的挑選势腮。

無后效性

如果一個(gè)問題被劃分各個(gè)階段之后买乃,階段I中的狀態(tài)只能由階段i-1中(或多個(gè)有限歷史階段)的狀態(tài)通過狀態(tài)轉(zhuǎn)移方程得來锈死,與其它狀態(tài)沒有關(guān)系,特別是與未發(fā)生的狀態(tài)沒有關(guān)系掸宛。

上述三項(xiàng)都在講同一個(gè)問題:該問題擁有遞推式死陆。該遞推式就是狀態(tài)轉(zhuǎn)移方程,該遞推式的等號(hào)右邊下標(biāo)都不會(huì)大于等號(hào)左邊唧瘾。

重疊子問題

動(dòng)態(tài)規(guī)劃將問題分解為子問題(一般兩個(gè))措译。然后子問題又會(huì)分解出子子問題,這些子問題的解都被存儲(chǔ)起來劈愚,相同子問題的解將被重復(fù)利用瞳遍。
這些子問題解的的時(shí)候從多個(gè)備選結(jié)果中選擇最優(yōu)的那個(gè)(比如最小,最大菌羽,之類的)掠械。

備忘錄

應(yīng)該是所有動(dòng)態(tài)規(guī)劃為提中必備的一項(xiàng)。
該項(xiàng)是子問題注祖,也就是存儲(chǔ)了每個(gè)狀態(tài)猾蒂。

  1. 鋼條切割:使用一維容器存儲(chǔ)了不同長(zhǎng)度鋼條的利潤(rùn)
  2. 矩陣鏈乘法:每個(gè)子問題都有一個(gè)開始位置和結(jié)束位置,所以需要使用二維容器來存儲(chǔ)是晨。
  3. 最長(zhǎng)公共子序列:每個(gè)字問題:第一個(gè)序列的結(jié)束位置肚菠,第二個(gè)序列的結(jié)束位置,所以也使用二維容器來存儲(chǔ)罩缴。

大白話

動(dòng)態(tài)規(guī)劃是一種尋找問題最優(yōu)解的方法蚊逢。
這種方法將一個(gè)問題從狀態(tài)的角度進(jìn)行定義,同時(shí)定義前一個(gè)狀態(tài)到后一個(gè)狀態(tài)的狀態(tài)轉(zhuǎn)移方程箫章,并存儲(chǔ)每一個(gè)狀態(tài)的最優(yōu)解烙荷。以便后續(xù)狀態(tài)使用。

上面這句話的意思其實(shí)就是:

保持問題給出的條件不變檬寂,但是改變問題的問法:

  1. 在鋼條切割問題中终抽,不同長(zhǎng)度鋼條的售價(jià)不變,但是將問題改為當(dāng)鋼條1米的時(shí)候,怎樣利潤(rùn)最大昼伴,2米的時(shí)候曾陽利潤(rùn)最大匾旭,3米,4米……一直到問題所問的米數(shù)圃郊。
  2. 矩陣鏈乘法中价涝,各個(gè)矩陣大小不變,但問題改為其中連續(xù)兩個(gè)矩陣相乘時(shí)計(jì)算歩數(shù)最小是多少持舆,連續(xù)三個(gè)飒泻,連續(xù)四個(gè),連續(xù)五個(gè)……一直到問題所問個(gè)數(shù)吏廉。
  3. 在最長(zhǎng)公共子序列中,兩個(gè)子序列不變惰许,將問題改為是否存在長(zhǎng)度為1,2,3,4,5…的子序列長(zhǎng)度席覆。
    改變問題的問法,同時(shí)也改變了解題的思路汹买。從之前的狀態(tài)推后來的狀態(tài)佩伤。

在自底而上的方式中(遞推)
這種方式的實(shí)現(xiàn)符合上述。
在自上而下的方式中(遞歸)
這種方式的實(shí)現(xiàn)也仍然是求的最開始的狀態(tài)然后推后來的狀態(tài)晦毙。

鋼條切割問題

一家公司購買長(zhǎng)鋼條生巡,將其切割成短鋼條出售,切割本身沒有成本见妒,長(zhǎng)度為i的短鋼條的價(jià)格為Pi孤荣。那給定一段長(zhǎng)度為n的鋼條和一個(gè)價(jià)格表Pi,求鋼條的切割方案使得收益Rn最大。如一個(gè)Pi如下:
長(zhǎng)度i 1 2 3 4 5 6 7 8 9 10
價(jià)格Pi 1 5 8 9 10 17 17 20 24 30

采用上述來分析:
鋼條長(zhǎng)度為:1 不切割须揣,2 不切割盐股, 3不切割 ,4 切割2+2耻卡,5 切割2+3……

同時(shí)應(yīng)該存儲(chǔ)每個(gè)狀態(tài)(鋼條長(zhǎng)度)的最大值:
自底而上

int cal(const std::vector<int> lp, int len)
{
    lpLen = lp.size() - 1;
    int num(0);                             //記錄循環(huán)次數(shù)
    std::vector<int> p(len+1);              //建立備忘
    for(int i=1; i <= len; i++)             //i代表長(zhǎng)度疯汁,從1開始建立備忘
    {
        tmp = 0;
        if ( i <= lpLen )                      //讓i<lpLen的時(shí)候,備忘錄還不可用
        {
            for(int j = 0 ; j <= i;j++)
            {
                num++;
                tmp  = tmp > lp[j] + lp[i-j] ? tmp : lp[j] + lp[i-j];
            }
        }else{                              //長(zhǎng)度>lpLen以后備忘錄已經(jīng)可用了卵酪。
            for(int j = 1 ; j < i/2 +1;j++)
            {
                num++;
                tmp  = tmp > p[j] + p[i-j] ? tmp : p[j] + p[i-j];
            }
        }
        p[i] = tmp;                         //當(dāng)確定找到了最大值幌蚊,才添加
    }
    std::cout<<"num : "<<num<<std::endl;
    return p[len];
}

在一邊存一邊讀取備忘錄的時(shí)候,備忘錄什么時(shí)候可用是一個(gè)坑:

  1. 剛開始溃卡,備忘錄中全是0溢豆,根本不能用。
  2. 只有當(dāng)基本的方式都全了以后才可用塑煎。
    也就是lp中可能的切割方式沫换。
  3. 當(dāng)長(zhǎng)度>能夠賣出的最長(zhǎng)長(zhǎng)度時(shí),不存在不切割這種情況,所以j從1開始讯赏。
    但是垮兑,當(dāng)長(zhǎng)度小于等于賣出的最長(zhǎng)長(zhǎng)度時(shí),是可以不切割的漱挎。

同時(shí)系枪,切割的長(zhǎng)度只需要從開始到總長(zhǎng)度的一半即可,因?yàn)榍懈钍菍?duì)稱的磕谅。

另外私爷,代碼中的lpLen使用其實(shí)是錯(cuò)的:
代碼中l(wèi)pLen假設(shè)的是基本的切割方式組合。但是實(shí)際中可能并不是和lpLen相等膊夹。
代碼中的lpLen應(yīng)該換成基本切割方式衬浑。比如如果將本題中5對(duì)應(yīng)的利潤(rùn)改為16,那么就會(huì)有11中基本切割方式。此時(shí)代碼的結(jié)果是錯(cuò)的放刨。
如果我們不能知道問題的輸入(也就是給定的條件)那么一般不能使用該方法工秩。

自上而下

//子上而下
int cut(const std::vector<int> &lp, std::vector<int> &p ,int len)
{
    int tmp = 0;    
    if(p[len] > 0) return p[len];       //使用備忘錄
    if(len == 0) return 0;              //0的時(shí)候出點(diǎn)

    if(len <= 10)
    {
        for(int i = 0;i <= len; i++)
        {
            tmp = std::max(tmp, lp[len-i]+lp[i]);
        }
    }else{
        for(int i = 1 ;i <= len/2 +1 ; i++)
        {                               //這里兩個(gè)都必須是cut*****
            tmp = std::max(tmp,cut(lp,p,i)+cut(lp,p,len-i) );
        }
    }
    p[len]=tmp;
    return p[len];
}
int calT(const std::vector<int> lp , int len)
{
    std::vector<int> p(len+1);
    return cut(lp,p,len);
}

其中標(biāo)*****的:
必須使用cut的原因是:此時(shí)備忘錄還沒建好,不能使用備忘錄进统。所以必須使用cut助币。

對(duì)比這兩種方式

  1. 兩種方式都需要先建立備忘錄。
  2. 子上而下的遞歸螟碎,并不需要去假設(shè)總共可能的基本切割方式眉菱。
    但是自底而上的遞推需要提前計(jì)算出基本的切割方式。

鋼條切割問題的最優(yōu)子結(jié)構(gòu)和狀態(tài)轉(zhuǎn)換方程:

//i表示未切割部分長(zhǎng)度掉分,j表示切割的長(zhǎng)度
f[i]=f[i-j]+max(f[j])

子問題重疊:
比如俭缓,固定米數(shù)鋼條的最大利潤(rùn)是一樣的。

矩陣鏈乘法

在該問題中叉抡,狀態(tài)和子問題為:從在給定的矩陣鏈中尔崔,取連續(xù)的1,2,3,4,5個(gè)矩陣相乘的歩數(shù)。
比如褥民,矩陣為:1015 1530 305 515 15*20
上述五個(gè)矩陣相乘季春。

當(dāng)取1個(gè)的時(shí)候相乘的計(jì)算歩數(shù) 為0 。
2個(gè)時(shí)消返,一共有4中情況
3個(gè)時(shí)载弄,一共有3中情況
4個(gè)時(shí),一共兩種情況
5個(gè)時(shí)撵颊,就一種情況

在3個(gè)矩陣相乘時(shí)宇攻,每一種情況都會(huì)用到2個(gè)矩陣相的結(jié)果,并從中選取最少歩數(shù)的結(jié)果倡勇。

#include <limits.h>
#include <iostream>
#include <algorithm>
#include <vector>

int mulM(const std::vector<int> vec)
{
    int len = vec.size();
    int tmp = 0;
    int vecNum = len - 1;                       //實(shí)際矩陣個(gè)數(shù)
    std::vector<std::vector<int>> memo(len);    //備忘錄

    for(int i = 0 ;i < memo.size() ; i++)
    {
            memo[i].resize(len);
    }

    for(int len = 2 ;len <=  vecNum  ; len++)   //狀態(tài):長(zhǎng)度從2開始逞刷。
    {                                           //長(zhǎng)為2的矩陣鏈在原矩陣鏈中的開始位置
        for(int start = 1 ; start <= vecNum - len + 1 ;start++ )
        {
            tmp=INT_MAX;
            int end = start + len -1;

            for(int k =  start ; k <= end -1  ; k++)    //括號(hào)的位置
            {
                int memo1=memo[start][k];
                int memo2=memo[k+1][end];
                tmp = std::min(tmp,vec[start-1] * vec[k] * vec[start+len-1] + memo1 + memo2);
                memo[start][end] = tmp;
            }
        }
    }
    for(int i = 0 ;i <= memo.size()-1 ; i++)
    {
        std::cout<<std::endl;
        for(int j = 0 ; j < memo[i].size(); j++)
        {
            std::cout<<memo[i][j]<<" ";
        }
        std::cout<<std::endl;
    }
    return memo[1][vecNum];
}

代碼講解

備忘錄中存儲(chǔ)的是memo[start][end]在原有矩陣鏈中從startend位置的子矩陣鏈相乘的最短歩數(shù)。

最長(zhǎng)公共子序列

該問題也有用最優(yōu)子結(jié)構(gòu):在某個(gè)公共子序列的基礎(chǔ)上推更長(zhǎng)的最優(yōu)子結(jié)構(gòu)。
這個(gè)的狀態(tài)轉(zhuǎn)換公式貌似不好寫吧夸浅?

狀態(tài)和子問題就變?yōu)椋寒?dāng)在兩個(gè)序列長(zhǎng)度從1開始慢慢增長(zhǎng)仑最,其公共子序列最大是多少?
那備忘錄也就是要記錄兩個(gè)公共子序列的長(zhǎng)度帆喇,所以需要一個(gè)二維容器警医。
備忘錄中memo[i][j],當(dāng)?shù)谝粋€(gè)序列長(zhǎng)i,第二個(gè)序列長(zhǎng)j情況下的最長(zhǎng)公共子系列坯钦。

這里其實(shí)也并不需要一個(gè)二外的容器预皇。遍歷仍然可以獲得。
這樣我們可以得到兩個(gè)序列的最大公共子序列的長(zhǎng)度婉刀,但是無法獲得其值吟温,所以有需要一個(gè)容器來存儲(chǔ)相等的部分。

#include <algorithm>
#include <iostream>
#include <vector>
#include <string>

std::string lcs(const std::string &l,const std::string &r)
{

    int ll = l.size();
    int rl = r.size();

    std::vector<std::vector<int> > memo(ll+1,std::vector<int>(rl+1));
    std::vector<std::vector<int> > memoRebuild(ll+1,std::vector<int>(rl+1));

    for(int inL = 1; inL <= ll ;inL ++)
    {
        for(int inR = 1; inR <= rl ;inR ++)
        {
            if(l[inL-1] == r[inR-1])
            {
                memo[inL][inR] = memo[inL-1][inR-1]+1;
                memoRebuild[inL][inR]=3;
            }else if(memo[inL][inR-1] > memo[inL-1][inR]){
                memo[inL][inR]=memo[inL][inR-1];
               memoRebuild[inL][inR]=2;
            }else{
                memo[inL][inR]=memo[inL-1][inR];
                memoRebuild[inL][inR]=1;
            }
       }
   }
    int reL = memoRebuild.size()-1;
    int reR = memoRebuild[0].size()-1;
    std::string re;
    while(reL >= 0 &&  reR >= 0)
    {
        if(memoRebuild[reL][reR]==3)
        {
            re.push_back(l[reL-1]);
            reL--; reR--;
        }else if(memoRebuild[reL][reR] == 1)
        {
            reL--;
        }else{
            reR--;
        }
    }
    std::cout<<"result ; "<<re<<std::endl;
    return re;
}

從上面的代碼中突颊,不難發(fā)現(xiàn)溯街,其實(shí)構(gòu)造好備忘錄才是本質(zhì)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市洋丐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌挥等,老刑警劉巖友绝,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肝劲,居然都是意外死亡迁客,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門辞槐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掷漱,“玉大人,你說我怎么就攤上這事榄檬〔贩叮” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鹿榜,是天一觀的道長(zhǎng)海雪。 經(jīng)常有香客問我,道長(zhǎng)舱殿,這世上最難降的妖魔是什么奥裸? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮沪袭,結(jié)果婚禮上湾宙,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好侠鳄,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布埠啃。 她就那樣靜靜地躺著,像睡著了一般畦攘。 火紅的嫁衣襯著肌膚如雪霸妹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天知押,我揣著相機(jī)與錄音叹螟,去河邊找鬼。 笑死台盯,一個(gè)胖子當(dāng)著我的面吹牛罢绽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播静盅,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼良价,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蒿叠?” 一聲冷哼從身側(cè)響起明垢,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎市咽,沒想到半個(gè)月后痊银,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡施绎,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年溯革,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谷醉。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡致稀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俱尼,到底是詐尸還是另有隱情抖单,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布遇八,位于F島的核電站臭猜,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏押蚤。R本人自食惡果不足惜蔑歌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揽碘。 院中可真熱鬧次屠,春花似錦园匹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至本昏,卻和暖如春供汛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涌穆。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工怔昨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宿稀。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓趁舀,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親祝沸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矮烹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 《算法導(dǎo)論》這門課的老師是黃劉生和張曙,兩位都是老人家了罩锐,代課很慢很沒有激情奉狈,不過這一章非常有意思。更多見:iii...
    mmmwhy閱讀 5,272評(píng)論 5 31
  • 動(dòng)態(tài)規(guī)劃應(yīng)用于子問題重疊的情況涩惑。對(duì)于公共子問題嘹吨,分治算法會(huì)做很多不必要的工作,它會(huì)反復(fù)求解公共子問題境氢。而動(dòng)態(tài)規(guī)劃算...
    LRC_cheng閱讀 432評(píng)論 0 1
  • 目錄 動(dòng)態(tài)規(guī)劃與分治法 2.動(dòng)態(tài)規(guī)劃求解的最優(yōu)化問題應(yīng)該具備的兩個(gè)要素2.1 最優(yōu)子結(jié)構(gòu)2.2 子問題重疊 動(dòng)態(tài)規(guī)...
    王偵閱讀 1,378評(píng)論 0 1
  • 動(dòng)態(tài)規(guī)劃(Dynamic Programming) 本文包括: 動(dòng)態(tài)規(guī)劃定義 狀態(tài)轉(zhuǎn)移方程 動(dòng)態(tài)規(guī)劃算法步驟 最長(zhǎng)...
    廖少少閱讀 3,279評(píng)論 0 18
  • 《梅雨來時(shí)》 文/知心 致命邂逅 于冰火兩重天 必然會(huì)升起一柄利劍 來斬?cái)噤宦湎碌乃寄?用等來守候冷酷到底吧 用...
    知心js閱讀 241評(píng)論 0 1