1. 程式人生 > >動態規劃(2):動態規劃的三種形式

動態規劃(2):動態規劃的三種形式

例:數字三角形(POJ 1163)

Language:DefaultThe Triangle
Time Limit: 1000MSMemory Limit: 10000K
Total Submissions: 45053Accepted: 27208

Description

7
3 8
8 1 0
2 7 4 4
4 5 2 6 5

(Figure 1)
Figure 1 shows a number triangle. Write a program that calculates the highest sum of numbers passed on a route that starts at the top and ends somewhere on the base. Each step can go either diagonally down to the left or diagonally down to the right.

Input

Your program is to read from standard input. The first line contains one integer N: the number of rows in the triangle. The following N lines describe the data of the triangle. The number of rows in the triangle is > 1 but <= 100. The numbers in the triangle, all integers, are between 0 and 99.

Output

Your program is to write to standard output. The highest sum is written as an integer.

Sample Input

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

Sample Output

30

Source

基本思路

用二維陣列存放數字三角形。

D( r, j) : 第r行第 j 個數字(r,j從1開始算)
MaxSum(r, j) : 從D(r,j)到底邊的各條路徑中,
最佳路徑的數字之和。
問題:求 MaxSum(1,1)

典型的遞迴問題。
D(r, j)出發,下一步只能走D(r+1,j)或者D(r+1, j+1)。故對於N行的三角形:

if ( r == N)
    MaxSum(r,j) = D(r,j)
else
    MaxSum( r, j) = Max{ MaxSum(r+1,j), MaxSum(r+1,j+1) } + D(r,j)

然後你會發現:
這裡寫圖片描述

為什麼超時

重複計算

如果採用遞規的方法,深度遍歷每條路徑,存在大量重複計算。則時間複雜度為 2n,對於 n = 100 行,肯定超時。

改進

如果每算出一個MaxSum(r,j)就儲存起來,下次用到其值的時候直接取用,則可免去重複計算。那麼可以用O(n2)時間完成計算。因為三角形的數字總數是 n(n+1)/2

改進程式碼

#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;

int D[MAX][MAX]; int n;
int maxSum[MAX][MAX];

int MaxSum(int i, int j)
 {
    if (maxSum[i][j] != -1)
        return maxSum[i][j];
    if (i == n) maxSum[i][j] = D[i][j];
    else {
        int x = MaxSum(i + 1, j);
        int y = MaxSum(i + 1, j + 1);
        maxSum[i][j] = max(x, y) + D[i][j];
    }
    return maxSum[i][j];
}

int main()
{ 
    int i, j; 
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= i; j++) 
        { 
            cin >> D[i][j]; 
            maxSum[i][j] = -1;
        }
    cout << MaxSum(1, 1) << endl;
}

轉化為遞推

“人人為我”型遞推

#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;

int D[MAX][MAX]; int n;
int maxSum[MAX][MAX];

int main()
{
    int i, j;
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= i; j++)
            cin >> D[i][j];
    for (int i = 1; i <= n; ++i)
        maxSum[n][i] = D[n][i];
    for (int i = n - 1; i >= 1; --i)
        for (int j = 1; j <= i; ++j)
            maxSum[i][j] = max(maxSum[i + 1][j], maxSum[i + 1][j + 1]) + D[i][j];
            cout << maxSum[1][1] << endl;
}

這裡說的人人為我型遞迴就是在求DP[i][j]的過程中, 使用DP[i-1][1]→DP[i - 1][i - 1]來推到出DP[i][j];

“我為人人”型遞迴

#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;

int D[MAX][MAX]; int n;
int maxSum[MAX][MAX];

int main()
{
    int i, j;
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= i; j++)
            cin >> maxSum[i][j];

    for (int i = 1; i < n; i++)
    {
        for (int j = 1; j <= i; j++)
        {
            maxSum[i + 1][j] = max(maxSum[i + 1][j], maxSum[i + 1][j] + maxSum[i][j]);
            maxSum[i + 1][j + 1] = max(maxSum[i + 1][j + 1], maxSum[i + 1][j + 1] + maxSum[i][j]);
        }
    }
    int maxx = -9999999999;
    for (int i = 1; i <= n; i++)
        maxx = max(maxx, maxSum[n][i]);
    cout << maxx << endl;
}
這裡說的人人為我型遞迴就是在求DP[i][j]的過程中, 求出DP[i + 1][j]和DP[i + 1][j +1]的其中一個解。

空間優化

沒必要用二維maxSum陣列儲存每一個MaxSum(r,j),只要從底層一行行向上遞推,那麼只要一維陣列maxSum[100]即可,即只要儲存一行的MaxSum值就可以。

進一步考慮,連maxSum陣列都可以不要,直接用D的第n行替代maxSum即可。

節省空間,時間複雜度不變
#include <iostream>
#include <algorithm>
#define MAX 101
using namespace std;

int D[MAX][MAX];
int n; int * maxSum;

int main() 
{
    int i, j;
    cin >> n;
    for (i = 1; i <= n; i++)
        for (j = 1; j <= i; j++)
            cin >> D[i][j];
    maxSum = D[n]; //maxSum指向第n行
    for (int i = n - 1; i >= 1; --i)
        for (int j = 1; j <= i; ++j)
            maxSum[j] = max(maxSum[j], maxSum[j + 1]) + D[i][j];
    cout << maxSum[1] << endl;
}

·
·
·

總結三種動態規劃的形式

人人為我

這裡寫圖片描述

狀態i的值Fi由若干個值已知的狀態值Fk,Fm,..Fy推出,如求和,取最大值

我為人人

這裡寫圖片描述

狀態i的值Fi在被更新(不一定是最終求出)的時候,依據Fi去更新(不一定是最終求出)和狀態i相關的其他一些狀態的值Fk,Fm,..Fy

注意:在選取最優備選狀態的值Fm,Fn,…Fy時,有可能有好的演算法或資料結構可以用來顯著降低時間複雜度

記憶型遞迴

優點:只經過有用的狀態,沒有浪費。遞推型會檢視一些沒用的狀態,有浪費

缺點:可能會因遞迴層數太深導致爆棧,函式呼叫帶來額外時間開銷。無法使用滾動陣列節省空間。總體來說,比遞推型慢。