動態規劃 ———— 鋼條切割到底在切啥?

      暫先不看問題自己,先來了解一下什麼叫動態規劃。從英文的dynamic programming來看彷佛並無「規劃」的意思在裏邊。可是,這裏的programming並不是指的是編程,而是指的一種表格法,這種表格法旨在一步步詳細分解問題,使之細化並最終得到問題的解。因此咱們稱之爲「規劃」。ios

       動態規劃和分治法相似,都是將大問題分解成小的子問題。但分治法自己的小問題每每是獨立的,而動態規劃的小的子問題依賴於大問題。算法

       動態規劃方法一般用來求解最優化問題(optimization problem)。這類問題一般能夠有不少個解,例如鋼條切割能夠有不少中切割方法一樣達到最大收益。編程

一般按以下4個步驟設計一個動態規劃算法:
1. 刻畫一個最優解的結構特徵。
2. 遞歸定義最優解的值
3. 計算最優解的值,一般採用自底向上的方法。
4. 利用計算出的信息構造一個最優解。
 函數

長度爲n英寸的鋼條共有2^n-1種不一樣的方法(這裏認爲對稱切割屬於不一樣的方法)。工具


這裏以n = 9 爲例,則總共有256種切割方案。優化

分別查看幾種方案:設計

第一種:code

 收益: 1 + 20 = 21遞歸

第二種:內存

收益: 10 + 10 = 20 比第一種少1

第三種:

收益: 1 * 9 = 9 顯然是收益最少的

綜合來看,收益最大的應該是 17 + 8 = 25,即分紅6和3

 

算法實現:

1. 自頂向下遞歸實現

2. 帶備忘的自頂向下法

3. 自底向上法

 


幾種方法的比較:
        切鋼條只是一個引子,切的過程就是對應動態規劃的不一樣的規模下子問題的求解過程。用不用遞歸併非動態規劃的本質。遞歸只是一種方法或者工具,而不是一種思想。自底向上的方法就沒有用到函數遞歸。
        樸素的遞歸算法之因此效率很低,是由於它反覆求解相同的子問題。比方長度爲33的鋼條能夠有2^32 = 4294967296種切割方法。用樸素的遞歸方法,須要求解這麼大的一個規模,且不說頻繁調用函數所產生的花銷,要計算10億次以上的加法和比較,這自己就很消耗時間。
        基於此,動態規劃方法仔細地安排求解順序,對每個子問題都只求解一次,並將值保存起來。若是以後再有求此子問題即可以查詢其值而不是從新再求一遍。帶備忘的動態規劃法須要額外的內存開銷,可是節省的時間倒是可觀的:可能將一個指數時間的解轉化爲多項式時間的解。

C++代碼實現:

#include<iostream>
#include<climits>
#include<ctime>
#include<cstdlib>
int price[] = {1,5,8,9,10,17,17,20,24,30,32,33,33,39,41,44,45,45,45,48,50,55,60,70,78,79,79,88,90,91,92,92,92};

int max(int a,int b)
{
   return a>b?a:b;
}

int cut_rod(int p[],int n)
{
  if(n == 0) return 0;
   
  int q = INT_MIN;
  
  for(int i = 1; i <= n; i++){
    q = max(q,p[i-1] + cut_rod(p,n-i));
  }
return q;
}

int cut_rod_memoized(int p[],int n,int r[])
{
   if(r[n-1] >= 0) return r[n-1];
   int q;
   if(n == 0){
      q = 0;
   }
   else{
      q = INT_MIN;
   }
   for(int i=1;i <= n; i++)
      q = max(q,p[i-1]+cut_rod_memoized(p,n-i,r));
r[n-1] = q;
return q;
}

int cut_rod_memoized_core(int p[],int n)
{
   int r[n];
   for(int i=0;i < n;i++){
     r[i] = INT_MIN;
   }
return cut_rod_memoized(p,n,r);
}

int bottom_up_cut_rod(int p[],int n)
{
   int r[n];
   
  
   for(int j = 1;j <= n; j++){
     int q = INT_MIN;
     for(int i = 1; i <= j; i++){
       if(j-1 -i >= 0){
         q = max(q,p[i-1] + r[j-1 - i]);
       } 
       else{
         q = max(q,p[i-1]);
       }
     }
     r[j-1] = q;
   }
return r[n-1];
}


int main(int argc,char* argv[])
{
   if(argc != 2) return -1;
   time_t start_time = time(NULL);
   int condition = atoi(argv[1]);
   switch(condition){
       case 0:std::cout<<cut_rod(price,sizeof(price)/sizeof(int))<<std::endl; break;
       case 1:std::cout<<cut_rod_memoized_core(price,sizeof(price)/sizeof(int))<<std::endl; break;
       case 2:std::cout<<bottom_up_cut_rod(price,sizeof(price)/sizeof(int))<<std::endl; break;
       default:break;
   }
   time_t end_time   = time(NULL);
   std::cout<<"use:"<<end_time-start_time<<"seconds"<<std::endl;
return 0;
}

單純的遞歸實現須要大概100秒的時間才能算出來,而另兩種只須要不到1秒時間。這就能夠看出動態規劃的威力了。

相關文章
相關標籤/搜索