动态规划初步

一、递归计算

#include <iostream>
using namespace std;

/* 问题: 由非负组成的三角形,第一行只有一个数,除了最下行之外
 * 每个数的左下方和右下方各有一个数,从第一行的数开始,每次可以
 * 往左下或右下走一格,直到走到最下行,沿途的数相加,怎么走和最大?
 */
const int N = 4;
const int a[N][N] = {{1},
                     {3, 2},
                     {4, 10, 1},
                     {4, 3, 2, 20}};

/* 把当前位置看成一个状态(i, j),定义状态的指标函数d(i, j)
 * 原问题的解是d(0, 0),
 * 状态转移方程: d(i, j) = a(i, j) + max{d(i+1, j), d(i+1, j+1)}
 */
int d(int i, int j)
{
    if(i == N)  //走到底
    {
        return 0;
    }
    if(j > i)   //边界处理
    {
        return 0;
    }
    cout << "(" << i + 1 << ", " << j + 1 << ")" << endl;

    int x = d(i+1, j);
    int y = d(i+1, j+1);
    return a[i][j] + (x > y ? x : y);
    // 如果return语句这么写的话会导致一次重复计算
    //return a[i][j] + (d(i+1, j) > d(i+1, j+1) ? d(i+1, j) : d(i+1, j+1));
}

int main()
{
    /*
    for(int i = 0; i < N; i++)
    {
        for(int j = 0; j < N; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }
    */
    cout << d(0, 0) << endl;

    return 0;
}

运行结果:

(1, 1) (2, 1) (3, 1) (4, 1) (4, 2) (3, 2) (4, 2) (4, 3) (2, 2) (3, 2) (4, 2) (4, 3) (3, 3) (4, 3) (4, 4)

24

Process returned 0 (0x0)   execution time : 0.599 s Press any key to continue.

结论:用直接递归的方法计算状态转移方程效率十分低下

原因:相同的子问题被重复计算了多次,(3, 2), (4, 2), (4, 3)

 

二、递推计算

#include <iostream>
using namespace std;

/* 问题: 由非负组成的三角形,第一行只有一个数,除了最下行之外
 * 每个数的左下方和右下方各有一个数,从第一行的数开始,每次可以
 * 往左下或右下走一格,直到走到最下行,沿途的数相加,怎么走和最大?
 */
const int N = 4;
const int a[N][N] = {{1},
                     {3, 2},
                     {4, 10, 1},
                     {4, 3, 2, 20}};

/* 把当前位置看成一个状态(i, j),定义状态的指标函数d(i, j)
 * 原问题的解是d(0, 0),
 * 状态转移方程: d(i, j) = a(i, j) + max{d(i+1, j), d(i+1, j+1)}
 */
int main()
{
    /*
    for(int i = 0; i < N; i++)
    {
        for(int j = 0; j < N; j++)
            cout << a[i][j] << " ";
        cout << endl;
    }
    */
    int d[N][N] = {0};
    for(int j = 0; j <= N; j++)
        d[N-1][j] = a[N-1][j];      //先处理最下行

    for(int i = N-2; i >= 0; i--)   //从下往上递推
    {
        for(int j = 0; j <= i; j++) //从左往右枚举了列
        {
            // 每个点的权重由下方和右下方的点推出
            d[i][j] = a[i][j] + (d[i+1][j] > d[i+1][j+1] ? d[i+1][j] : d[i+1][j+1]);
        }
    }
    cout << d[0][0] << endl;
    return 0;
}

运行结果:

24

Process returned 0 (0x0)   execution time : 0.285 s Press any key to continue.

结论:程序的时间负责度为 O(n^2),递推的关键是边界和计算顺序,

原因:i 是逆序枚举的,因此在计算d[i][j]前,她所需要的d[i+1][j]和d[i+1][j+1]一定已经计算出来了

 

三、记忆化搜索

#include <iostream>
#include <cstring>
using namespace std;

/* 问题: 由非负组成的三角形,第一行只有一个数,除了最下行之外
 * 每个数的左下方和右下方各有一个数,从第一行的数开始,每次可以
 * 往左下或右下走一格,直到走到最下行,沿途的数相加,怎么走和最大?
 */
const int N = 4;
const int a[N][N] = {{1},
                     {3, 2},
                     {4, 10, 1},
                     {4, 3, 2, 20}};

/* 把当前位置看成一个状态(i, j),定义状态的指标函数d(i, j)
 * 原问题的解是d(0, 0),
 * 状态转移方程: d(i, j) = a(i, j) + max{d(i+1, j), d(i+1, j+1)}
 */
int ans[N][N];   //递归中的计算结果保存在这里
int d(int i, int j)
{
    if(i == N)          //走到底
        return 0;
    if(j > i)           //边界处理
        return 0;
    if(ans[i][j] >= 0)  //已经计算过
        return ans[i][j];

    cout << "(" << i + 1 << ", " << j + 1 << ")" << endl;

    int x = d(i+1, j);
    int y = d(i+1, j+1);
    return ans[i][j] = a[i][j] + (x > y ? x : y);
}

int main()
{
    //初始化为-1
    memset(ans, -1, sizeof(ans));

    cout << d(0,0);

    return 0;
}

运行结果:

(1, 1)
(2, 1)
(3, 1)
(4, 1)
(4, 2)
(3, 2)
(4, 3)
(2, 2)
(3, 3)
(4, 4)
24
Process returned 0 (0x0)   execution time : 0.144 s
Press any key to continue.

结论:由于不相同的节点一共只有O(n^2)个,无论以怎样的顺序访问,时间复杂度均为O(n^2)

     当采用记忆化搜索时,不必事先确定各状态的计算顺序,但需要记录每个状态“是否已经计算过”。

原因:程序递归同时把结果保存在数组中,题目中各个数都是非负的,因此如果计算过某个d[i][j],则她应该是非负的,

     这样只需把所有d初始化为-1,既可通过判断是否d[i][j]>=0 得知她是否已经被计算过。

posted @ 2013-10-26 11:11  瓶哥  Views(111)  Comments(0Edit  收藏  举报