動態規劃:循環 vs 記憶化搜索.mdp

 
## 動態規劃兩種實現方式
根據狀態轉移,保留中間的計算結果,這種求解問題的算法叫作*動態規劃(Dynamic Programming,DP)*,經過空間存取中間結果,避免中間結果的屢次求解,從而節省程序的運行時間,是動態規劃的主要特變。
 
典型的動態規劃算法,實現方式有兩種
 
記憶化搜索
自底向上的循環
 
### 記憶化搜索
實現方式相似遞歸,不過在具體求解前先判斷是否是已經算過了,若是算過了,直接返回。
```cpp
int ans[M][N];
memset(ans, -1, sizeof(ans));
 
int solve(int m,int n){
     if(ans[m][n]==-1) {
          solve(m, n);
         return ans[m][n];
    }
    //judge whether current m,n is corner cases, if so, deal with it and return
    if(m<0||n<0) return XX;
    if(m==0 || n==0) {
         ans[m][n]=YY; 
         return ans[m][n;
    }
    //solve(m,n) according to recursive formula
    //set ans[m][n] to solved answer
    ans[m][n] = solve(m-1,n)+solve(m-2,n)+..+solve(m-p,n) +
                    solve(m,n-1)+solve(m,n-2)+..+solve(m,n-q) +
                   ...;
    return ans[m][n];      
}
 
```
 
* 優點:實現邏輯簡單(相似遞歸),直接根據狀態轉移的邏輯,處理邊界狀況便可。
* 劣勢:若某個狀態計算時涉及的狀態數較多,可能會形成棧溢出。
 
 
###自底向上的循環
從初始狀況 -> 複雜的case -> 最終的結果, 由遞推式自底向上。
好比,考慮下面的遞推式
$$ f(m,n)=f(m-1,n)+f(m,n-1), 1\le m < M, 1\le n < N$$
$$ f(0,n)=f(m,0)=1$$
用自底向上的循環實現以下
```cpp
int solve(){
    int f[M][N];
    for(i=0; i<M;i++)
         f[i][0]=1;
    for(i=0; i<N;i++)
         f[0][i]=1;
    for(i=1;i<M;i++)
         for(j=1;j<N;j++)
              f[i][j] = f[i-1][j]+f[i][j-1];
    return f[M-1][N-1];
}
```
* 優點,循環實現,效率會比遞歸實現高些,且不會形成棧的溢出
* 劣勢,某些狀況下,若是狀態轉移過於複雜,可能不太容易寫成清晰的循環形式
好比像這種
$$ f(n) = F (f(g(n))) , n\ne n_1,n_2,..n_k$$
$$ f(n_i)=p_i$$ 
這種時候,可能並不太好造成一個循環形式,可能就得遞歸實現。



相關文章
相關標籤/搜索