1. 程式人生 > >動態規劃&備忘錄方法&遞迴方法

動態規劃&備忘錄方法&遞迴方法

動態規劃的基本思想是,將原問題拆分為若干子問題,自底向上的求解。其總是充分利用重疊子問題,即通過每個子問題只解一次,把解儲存在一個表中,巧妙的避免了子問題的重複求解。

遞迴方法,採用的是自頂向下的思想,拆分為若干子問題,但是造成了子問題的重複求解。

備忘錄方法,採用的也是自頂向下的思想,但是該方法維護了一個記錄子問題解的表,雖然填表動作的控制結構更像遞迴方法,但是的確避免了子問題的重複求解。

下面以字串的相似度來展示一下各方法的特點:

動態規劃

遞迴:略

備忘錄:

  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. usingnamespace std;  
  5. constint N = 100;  
  6. int dist[N][N];  
  7. int minValue(int va, int vb, int vc)  
  8. {  
  9.     int temp = va;  
  10.     if(vb < temp)  
  11.         temp = vb;  
  12.     if(vc < temp)  
  13.         temp = vc;  
  14.     return temp;  
  15. }  
  16. int distance(string strA, int pABegin, int pAEnd, string strB, int
     pBBegin, int pBEnd)  
  17. {  
  18.     if(dist[pABegin][pBBegin]>=0)  
  19.         return dist[pABegin][pBBegin];  
  20.     if (pABegin > pAEnd)  
  21.     {  
  22.         if(pBBegin > pBEnd)  
  23.             dist[pABegin][pBBegin] = 0;  
  24.         else
  25.             dist[pABegin][pBBegin] = pBEnd - pBBegin + 1;  
  26.         return
     dist[pABegin][pBBegin];  
  27.     }  
  28.     if (pBBegin > pBEnd)  
  29.     {  
  30.         if(pABegin > pAEnd)  
  31.             dist[pABegin][pBBegin] = 0;  
  32.         else
  33.             dist[pABegin][pBBegin] = pAEnd - pBBegin + 1;  
  34.         return dist[pABegin][pBBegin];  
  35.     }  
  36.     if (strA[pABegin]==strB[pBBegin])  
  37.     {  
  38.         dist[pABegin][pBBegin] = distance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);  
  39.         return dist[pABegin][pBBegin];  
  40.     }  
  41.     else
  42.     {  
  43.         int t1 = distance(strA, pABegin, pAEnd, strB, pBBegin+1, pBEnd);  
  44.         int t2 = distance(strA, pABegin+1, pAEnd, strB, pBBegin, pBEnd);  
  45.         int t3 = distance(strA, pABegin+1, pAEnd, strB, pBBegin+1, pBEnd);  
  46.         dist[pABegin][pBBegin] = minValue(t1, t2, t3)+1;  
  47.         return dist[pABegin][pBBegin];  
  48.     }  
  49. }  
  50. int main()  
  51. {  
  52.     string A;  
  53.     string B;  
  54.     cin>>A;  
  55.     cin>>B;  
  56.     for (int i=0; i<N; i++)  
  57.         for(int j=0; j<N; j++)  
  58.             dist[i][j] = -1;  
  59.     cout<<distance(A, 0, A.length()-1, B, 0, B.length()-1);  
  60.     return 0;  
  61. }  

當n=0時,f(n) = 0     

當n=1時,f(n) = 1

當n>1時,f(n) = f(n-1) + f(n-2)

遞迴演算法:

  1. int fun(int n)  
  2. {  
  3.     if(n <= 0)  
  4.         return 0;  
  5.     if(n == 1)  
  6.         return 1;  
  7.     return fun(n-1)+fun(n-2);  
  8. }  

備忘錄方法:
  1. #include <iostream>
  2. usingnamespace std;  
  3. constint N = 100;  
  4. int f[N];  
  5. int fun(int n)  
  6. {  
  7.     if(f[n]>=0)  
  8.         return f[n];  
  9.     if(n == 0)  
  10.     {  
  11.         f[0] = 0;  
  12.         cout<<"0"<<endl;  
  13.         return f[0];  
  14.     }  
  15.     if(n == 1)  
  16.     {  
  17.         f[1] = 1;  
  18.         cout<<"1"<<endl;  
  19.         return f[1];  
  20.     }  
  21.     cout<<n<<endl;  
  22.     f[n] = fun(n-1) + fun(n-2);  
  23.     return f[n];  
  24. }  
  25. int main()  
  26. {  
  27.     for (int i=0; i<N; i++)  
  28.         f[i] = -1;  
  29.     cout<<fun(4);  
  30.     return 0;  
  31. }  

由於計算的時候只需要前兩個數即可,所以程式碼還可以繼續優化。但是對於上述的備忘錄方法貌似不能繼續進行空間優化了(不知道對否,如果理解的不對請不吝賜教~)。

但是對於下面的方法(就稱為遍歷方法吧),還是可以繼續優化的。

  1. #include <iostream>
  2. usingnamespace std;  
  3. constint N = 100;  
  4. int f[N];  
  5. int main()  
  6. {  
  7.     int n;  
  8.     cin>>n;  
  9.     for (int i=0; i<=n; i++)  
  10.     {  
  11.         if(i==0)  
  12.             f[i] = 0;  
  13.         elseif(i==1)  
  14.             f[i] = 1;  
  15.         else
  16.             f[i] = f[i-1] + f[i-2];  
  17.     }  
  18.     cout<<f[n];  
  19.     return 0;  
  20. }  
由於計算的時候只用了前兩個數,所以沒有必要使用陣列。
  1. #include <iostream>
  2. usingnamespace std;  
  3. int main()  
  4. {  
  5.     int n;  
  6.     cin>>n;  
  7.     int temp1, temp2, temp;  
  8.     for (int i=0; i<=n; i++)  
  9.     {  
  10.         if(i==0)  
  11.             temp1 = 0;  
  12.         elseif(i==1)  
  13.             temp2 = 1;  
  14.         else
  15.         {  
  16.             temp = temp1 + temp2;  
  17.             temp1 = temp2;  
  18.             temp2 = temp;  
  19.         }  
  20.     }  
  21.     cout<<temp;  
  22.     return 0;  
  23. }  

總結:從程式碼中可以看出來,遍歷方法實際上是一種自底向上的方法,而備忘錄方法是一種自頂向下的方法,也許正由於這個原因造成了備忘錄方法無法進行空間優化。(待證)

 動態規劃演算法的基本要素: 
1  最優子結構性質
當問題的最優解包含了其子問題的最優解時,稱該問題具有最優子結構性質。
2  重疊子問題性質   
動態規劃演算法對每個問題只解一次,將其解儲存在一個表格中,當再次需要解此問題時,用常數時間檢視一下結果。因此,用動態規劃演算法通常只需要多項式時間。

備忘錄方法:
•用一個表格來儲存已解決的子問題的答案,用的時候查表即可。 
•採用的遞迴方式是自頂向下。
•控制結構與直接遞迴相同,區別在於備忘錄方式為每個解過的子問題建立備忘錄。 
•初始化為每個子問題的記錄存入一個特殊的值,表示並未求解。在求解過程中,檢視相應記錄如果是特殊值,表示未求解,否則只要取出該子問題的解答即可。

備忘錄方法與動態規劃和遞迴的區別:

1、動態規劃是自低向上 ,備忘錄方法是自頂向下,遞迴是自頂向下

2、動態規劃每個子問題都要解一次,但不會求解重複子問題;備忘錄方法只解哪些確實需要解的子問題;遞迴方法每個子問題都要解一次,包括重複子問題 。

動態規劃解矩陣連乘問題

#include<iostream>
using namespace std;
void metrixchain(int n,int p[],int **s,int **m)
{
 for(int i=0;i<n;i++)
  m[i][i]=0;
 for(i=2;i<=n;i++)    //小於等於n
 {
  for(int j=0;j<n-i+1;j++) //橫座標
  { int k=j+i-1;   //縱座標
  m[j][k]=m[j+1][k]+p[j]*p[k+1]*p[j+1];s[j][k]=j;
  for(int t=j+1;t<k;t++)
  {
   int u=m[j][t]+m[t+1][k]+p[j]*p[t+1]*p[k+1];
   if(u<m[j][k])
   {
    m[j][k]=u;s[j][k]=t;
   }
  }
  }
 }
}
void Traceback(int i, int j, int ** s)
{
 if (i==j||i==j-1) return;
 cout<<"divide after metrix "<<s[i][j]<<endl;
 Traceback(i, s[i][j],  s);
 Traceback(s[i][j]+1,j ,  s);
}


int main()
{
 int p[]={30,35,15,5,10,20,25};
 int **s=new int*[6];
 for(int i=0;i<6;i++)
  s[i]=new int[6];
 int **m=new int*[6];
 for( i=0;i<6;i++)
  m[i]=new int[6];
 metrixchain(6,p,s,m);
 for( i=0;i<6;i++)
 { for( int j=i;j<6;j++)
 cout<<m[i][j]<<" ";
 cout<<endl;
 }
 Traceback(0,5,s);
 return 0;

}