動態規劃算法典型問題

1. 什麼是動態規劃?
         和分治法一樣,動態規劃(dynamicprogramming)是通過組合子問題而解決整個問題的解。
         分治法是將問題劃分成一些獨立的子問題,遞歸地求解各子問題,然後合併子問題的解。
         動態規劃適用於子問題不是獨立的情況,也就是各子問題包含公共的子子問題。
         此時,分治法會做許多不必要的工作,即重複地求解公共的子問題。動態規劃算法對每個子問題只求解一次,將其結果保存起來,從而避免每次遇到各個子問題時重新計算答案。
2. 動態規劃算法的設計
兩種方法:
         自頂向下(又稱記憶化搜索、備忘錄):基本上對應着遞歸函數實現,從大範圍開始計算,要注意不斷保存中間結果,避免重複計算
         自底向上(遞推):從小範圍遞推計算到大範圍
動態規劃的重點:
         遞歸方程+邊界條件
3. 爬樓梯問題
         一個人每次只能走一層樓梯或者兩層樓梯,問走到第80層樓梯一共有多少種方法。
         設DP[i]爲走到第i層一共有多少種方法,那麼DP[80]即爲所求。很顯然DP[1]=1, DP[2]=2(走到第一層只有一種方法:就是走一層樓梯;走到第二層有兩種方法:走兩次一層樓梯或者走一次兩層樓梯)。同理,走到第i層樓梯,可以從i-1層走一層,或者從i-2走兩層。很容易得到:
         遞推公式:DP[i]=DP[i-1]+DP[i-2]
         邊界條件:DP[1]=1   DP[2]=2
         (a)自頂向下的解法:

  1. long long dp[81] = {0};/*用於保存中間結果 
  2. 否則會重複計算很多重複的子問題*/  
  3. long long DP(int n)  
  4. {  
  5.     if(dp[n])  
  6.         return dp[n];  
  7.     if(n == 1)  
  8.         return 1;  
  9.     if(n == 2)  
  10.         return 2;  
  11.     dp[n] = DP(n-1) + DP(n-2);  
  12.     return dp[n];     
  13. }  

         (b)自底向上的解法:

  1. int i;  
  2. long long dp[81]; /* 注意當n超過75時,結果值將超過int範圍 */  
  3. dp[1] = 1;  
  4. dp[2] = 2;  
  5. for(i=3; i <= 80; i++)  
  6.     dp[i] = dp[i-1] + dp[i-2];  

4. 最長上升子序列
         對於序列:4 1 2 24,它的最長上升子序列是1 2 4,長度爲3。
         對於序列:4 2 4 25 6,它的最長上升子序列是2 4 5 6,長度爲4
         設a[i]表示原序列,設DP[i]表示以第i個數結尾的最長上升序列的長度,那麼很顯然想導出DP[i]的值,需要在DP[k](1<=k<i)中找出滿足a[k]<a[i]最大的一項。假設第kk項是我們找到的答案,那麼第i個數就可以接在第kk個數之後,成爲以第i個數結尾的最長升序列。如果沒有找到答案,換言之第i個數比前面的數都要小,那麼DP[i]=1,也即生成了從自己開始又以自己結尾的最長升序列。綜上,我們很容易得出:
         遞推公式:DP[i]=max(DP[k]+1,DP[i])  1<=k<i
         邊界條件:DP[i]=1                   1<=i<=n
         算法複雜度爲O(n^2)

  1. void RiseSequence(int Array[], int num)  
  2. {  
  3. #define MAX_LENGTH  30  
  4.     struct  
  5.     {  
  6.         int SequenceValue;  /* max length ending with this num */  
  7.         int PreviousIndex;  /* record the previous number */  
  8.     }ArrayInfo[MAX_LENGTH], temp;  
  9.     int i;  
  10.     for(i = 0; i < num; i++)  
  11.     {  
  12.         int j;  
  13.         ArrayInfo[i].SequenceValue = 1;  
  14.         ArrayInfo[i].PreviousIndex = -1;  
  15.         for(j = 0; j < i; j++)  
  16.         {  
  17.             if(Array[j] < Array[i] && (ArrayInfo[j].SequenceValue + 1 > ArrayInfo[i].SequenceValue))  
  18.             {  
  19.                 ArrayInfo[i].SequenceValue = ArrayInfo[j].SequenceValue + 1;  
  20.                 ArrayInfo[i].PreviousIndex = j;  
  21.             }  
  22.         }  
  23.     }  
  24.     temp.SequenceValue = ArrayInfo[0].SequenceValue;  
  25.     for(i = 1; i < num; i++)  
  26.     {  
  27.         if(temp.SequenceValue < ArrayInfo[i].SequenceValue)  
  28.         {  
  29.             temp.SequenceValue = ArrayInfo[i].SequenceValue;  
  30.             temp.PreviousIndex = i;  
  31.         }  
  32.     }  
  33.     for(i = 0; i < temp.SequenceValue; i++)  
  34.     {  
  35.         printf("%d  ", Array[temp.PreviousIndex]);  /* in reverse order */  
  36.         temp.PreviousIndex = ArrayInfo[temp.PreviousIndex].PreviousIndex;  
  37.     }  
  38.     printf("\nthe max rising sequence length is %d\n", temp.SequenceValue);  
  39. }  

5. 最長公共子序列
         給定兩個序列X和Y,稱序列Z是X和Y的公共子序列如果Z既是X的一個子序列,又是Y的一個子序列。例如,如果X={a,b,c,b,d,a,b} Y={b,d,c,a,b,a} 那麼序列{b,c,a}就是X和Y的一個公共子序列,但是它並不是X和Y的最長公共子序列,因爲它的長度爲3。而同爲X和Y公共子序列的{b,c,b,a},長度爲4,因爲找不到長度爲5或更大的公共子序列,所以X和Y的最長公共子序列長度就爲4。
         假設兩個序列數組分別爲a,b。定義f(i,j)爲計算到a數組第i個數、b數組第j個數時所得到的最長公共子序列的長度。這時有兩種情況:
         1.假如a[i]=b[j],那麼f(i,j)=f(i-1,j-1)+1
         2.假如a[i]!=b[j],那麼f(i,j)=max(f(i-1,j),f(i,j-1))
         邊界條件爲:f(i,0)=0     1<=i<=len(a)
                               f(0,j)=0     1<=j<=len(b)
         算法複雜度:O(n^2),len(a)表示數組a的長度。

發佈了25 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章