關於動態規劃的理解

動態規劃是個比較有趣的算法,第一次接觸動態規劃也是從一個比較特別的教程開始的,這裏貼出原文地址http://blog.csdn.net/woshioosm/article/details/7438834
看完原文回到這裏,其實我以爲不少像我這種C語言剛剛入門的人,只理解到了動態規劃的轉移方程,以原文中的例子爲例:
當mineNum = 0且people >= peopleNeeded[mineNum]時 f(people,mineNum) = gold[mineNum]
當mineNum = 0且people < peopleNeeded[mineNum]時 f(people,mineNum) = 0
當mineNum != 0時 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]與f(people, mineNum-1)中的較大者
其中,前兩個式子對應動態規劃的「邊界」,後一個式子對應動態規劃的「最優子結構」。
菜鳥的我以爲這個比較容易實現啊,因而有了下面的代碼:
 1 int goldGet(int People, int mineNum)  2 {  3      if(mineNum == 0)  4  {  5           /*考慮最後一座金礦是,若是剩餘開採金礦的總人數  6  多餘或者等於須要的人數,則返回該金礦的金子數  7  目,不然返回0。*/
 8           if(People >= PeopleNeeded[mineNum])  9  { 10                return Gold[mineNum]; 11  } 12           else
13  { 14                return 0; 15  } 16  } 17      else
18  { 19           int m, n; 20           /*考慮第mineNum座金礦,第一種狀況:開採該座金礦所能得到的最多的金子數目, 21  爲該座金礦的金子數目和剩下的人開採剩下的金礦所獲得的最多的金子數目的和 22  第二種狀況:部開採該座金礦所得到的最多的金子數目,是目前全部的人用來開 23  採剩餘的金礦得到的最多的金子數目。返回兩種狀況下金子數目較多的一種狀況 24  的金子數目。*/
25           if(People >= PeopleNeeded[mineNum]) 26  { 27                m = Gold[mineNum] + goldGet(People - PeopleNeeded[mineNum], mineNum - 1); 28  } 29           else
30  { 31                m = goldGet(People, mineNum - 1); 32  } 33           n = goldGet(People, mineNum - 1); 34 
35           return m >= n ? m : n; 36  } 37 }
後面的關於時間複雜度和空間複雜度的優化對我像我這種境界的人來講,這個理解有點困難,我就暫且自我安慰一下,告訴本身已經理解了動態規劃,而且把文章最後的金礦問題作了,而且經過了測試數據,所有代碼以下:
#include <stdio.h> #include <stdlib.h>

#define MaxNum 500

int PeopleNeeded[MaxNum]; //PeopleNeeded[i]表示第i座金礦須要的開採人數
int Gold[MaxNum]; //Gold[i]表示第i座金礦開採後獲得的金子數目

int goldGet(int People, int mineNum) { if(mineNum == 0) { /*考慮最後一座金礦是,若是剩餘開採金礦的總人數 多餘或者等於須要的人數,則返回該金礦的金子數 目,不然返回0。*/
          if(People >= PeopleNeeded[mineNum]) { return Gold[mineNum]; } else { return 0; } } else { int m, n; /*考慮第mineNum座金礦,第一種狀況:開採該座金礦所能得到的最多的金子數目, 爲該座金礦的金子數目和剩下的人開採剩下的金礦所獲得的最多的金子數目的和 第二種狀況:部開採該座金礦所得到的最多的金子數目,是目前全部的人用來開 採剩餘的金礦得到的最多的金子數目。返回兩種狀況下金子數目較多的一種狀況 的金子數目。*/
          if(People >= PeopleNeeded[mineNum]) { m = Gold[mineNum] + goldGet(People - PeopleNeeded[mineNum], mineNum - 1); } else { m = goldGet(People, mineNum - 1); } n = goldGet(People, mineNum - 1); return m >= n ? m : n; } } int main() { int People; //開採金礦的總人數
     int GoldNum; //金礦數目
     int GoldGet; //最終可以獲取的金子的總數


     int i, j; //用來進行的便利的變量
 scanf("%d %d", &GoldNum, &People); for(i = 0; i < GoldNum; i ++) { scanf("%d %d", &PeopleNeeded[i], &Gold[i]); } GoldGet = goldGet(People, GoldNum); printf("%d\n", GoldGet); return 0; }
固然事情尚未結束,由於我以爲我理解了動態規劃,因而跑到hihocoder上躍躍欲試(附hihocoder地址:http://hihocoder.com/,一個很不錯的OJ),菜鳥的我只敢嘗試01揹包那種簡單的題目。把代碼稍微修改了一下,符合了題目的輸入和輸出,很興奮的提交了,結果"Time Limit Exceeded"。很鬱悶,看來我仍是沒有真正的理解動態規劃,還須要把優化的那部分看懂,還好hihocoder上每道題都會對用到的算法進行講解,這回稍微弄懂了,修改了代碼提交,而後AC了,這裏先給出代碼:
/*01揹包問題*/ #include <stdio.h> #include <stdlib.h> #include <memory.h>

int max(int x, int y) { return x >=y ? x : y; } int main() { int N; //表示獎品的個數
     int M; //表示獎券數
     
     int need[500]; //need[i]表示第i個獎品須要的獎券數
     int value[500]; //value[i]表示第i個獎品的評分值
         
     int best[100000]; //best[j]表示對於i個獎品時j張獎券的最大評分值
     memset(best, 0, sizeof(best)); //初始化best的全部元素爲0

     int i, j; //程序進行遍歷的變量
 scanf("%d %d", &N, &M); for(i = 0; i < N; i ++) { scanf("%d %d", &need[i], &value[i]); } for(i = 0; i < N; i ++) { for(j = M - 1; j >= need[i]; j --) { best[j] = max(best[j], best[j - need[i]] + value[i]); } } printf("%d\n", best[M - 1]); return 0; }
嗯。。。這裏代碼貌似要斷了不少,對於前面的輸入輸出你們都看得懂,主要的是最後一個循環,其實這個循環也比較好理解,在我貼的第一個比較low的程序裏面,轉移方程是用遞歸實現的,這裏改爲了雙重循環,實際上是一個意思。最難理解的是:
best[j] = max(best[j], best[j - need[i]] + value[i]);
爲了理解這句話須要一步步的來,將一開始比較low的遞歸搞成雙重循環(這裏不是說遞歸比較low,而是說我寫的比較low)
for(i = N- 1; i > 0; i --) { for(j = M - 1; j >= need[i]; j --) { best[i, j] = max(best[i - 1][ j], best[i - 1][j - need[i]] + value[i]); } }
這裏i > 0是由於i = 0沒有討論的必要,其實嚴格的來講這個循環和前面的遞歸併不徹底同樣,這裏的時間複雜度更低一些,由於這裏的最高收益是用數組進行存儲的,對於某次循環中計算出了best[x][y]的值後,後面若是還須要用到就不須要再計算了,而以前的遞歸用的是函數,並不能存儲數值,效率更低。
這裏咱們能夠在複習一下轉移方程,
當i != 0時 bes[i][j] 是 best[i - 1][j - need[i]] + value[i]與best[i - 1][j]中的較大者
這裏咱們能夠發現best[Ai][Aj]依賴於best[Bi][Bj]時,確定有Ai = Bi + 1,以及Aj >= Bj。
舉個簡單的例子,咱們求best[5][j]的時候只與best[4][j]和best[4][j - need[5]]有關,對於前面的best[0][k],best[1][k],...best[3][k],其中k = 0,1,...M-1沒有任何關係,那麼咱們就沒有必要用那麼複雜的存儲。而且j的取值是從M-1到0,考慮到Aj >= Bj的規律,計算best[i][j]的時候,best[i-1][j+1,...M-1]已經沒有任何利用價值了,這些空間也是多餘的,怎麼辦呢,其實計算best[i][j - 1]的時候best[i - 1][j]就沒有價值了,那麼咱們計算best[i][j]的結果就能夠直接存放到best[i - 1][j]的地址上,這樣的話,咱們的存儲其實只須要一個大小爲M的一維數組。而且是best[i][j]覆蓋beat[i - 1][j],因此循環的時候i要從0到N-1,
因而循環就能夠寫成
for(i = 0; i < N; i ++) { for(j = M - 1; j >= need[i]; j --) { best[j] = max(best[j], best[j - need[i]] + value[i]); } }
這個就是咱們最後的結果。
相關文章
相關標籤/搜索