【C++动态规划学习总结】

1.动态规划:初识

动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

实际上是对暴力递归的一种优化,把已经计算过的信息保存起来,下次要用时不用重复计算,并且通过求解动态数组的方式代替了函数的递归调用。

第一步优化在于对计算结果的存储,避免重复计算。可以优化成下面描述的记忆化搜索方法。
第二步优化在于对递归函数调用的优化,直接从bace case 反推,而不是从上往下调用。


2.例子:

1.1机器人走路问题

机器人在有N个位置的路上走,开始机器人在M位置,机器人可左右移动,到1位置时只能右移,到N位置时只能左移,机器人从起始位置M到目标位置P,共走K步,求移动的方法数
参靠左神代码编写:

#include 
#include 
using namespace std;

/* 原始暴力递归
 * N:总位置数   cur:当前位置    rest:剩余步数  aim:目标位置
 * 返回: 从cur到 aim 的方法数
 */
int way1(const int N, int rest, int cur, const int aim)
{
    //第一步,返回边界
    if (rest == 0) // base case
    {
        return (cur == aim ? 1 : 0);
    }
    //第二步,看当前位置的分指数个数(分普通情况和特殊情况)
    //特殊情况,来到最左边时
    if (cur == 1)
    {
        return way1(N, rest - 1, 2, aim);
    }
    //来到最右端时
    if (cur == N)
    {
        return way1(N, rest - 1, N - 1, aim);
    }
    //来到中间位置时
    return way1(N, rest - 1, cur + 1, aim) + way1(N, rest - 1, cur - 1, aim);
}

//傻缓存法改进(记忆化搜素)
int way2(const int N, int rest, int cur, const int aim, vector<vector<int>> &dp) //注意传引用
{
    if (dp[cur][rest] != -1)
    {
        return dp[cur][rest];
    }
    if (rest == 0) //边界条件 步数走完当前值到达目标点时有一种方法,否则无
    {
        return cur == aim ? 1 : 0;
    }
    //如果此条信息计算过,则直接返回dp数组里存的值

    int ret = 0; //存返回值,用来给dp数组赋值
    if (cur == 1)
    {
        ret = way2(N, rest - 1, 2, aim, dp);
    }
    else if (cur == N)
    {
        ret = way2(N, rest - 1, N - 1, aim, dp);
    }
    else
    {
        ret = way2(N, rest - 1, cur - 1, aim, dp) + way2(N, rest - 1, cur + 1, aim, dp);
    }
    dp[cur][rest] = ret;
    return ret;
}

//动态规划法
int way3(const int N, int K, int start, const int aim)
{
    // dp[x1][x2] 表示到达 x1 时,还剩 x2 步走,有几种可行方法
    vector<vector<int>> dp(N + 1, vector<int>(K + 1, 0)); //初始化 dp 数组全为0
    dp[aim][0] = 1;                                       // base case 当到达 aim 时还剩 0 步,返回值为存在 dp 数组中
    for (int rest = 1; rest <= K; ++rest)                 //遍历每种步数
    {
        dp[1][rest] = dp[2][rest - 1];    //到达第一位置时只能往右移
        for (int cur = 2; cur < N; ++cur) //遍历每个位置点
        {
            dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1]; //中间位置可以往两边走
        }
        dp[N][rest] = dp[N - 1][rest - 1]; //最右位置,只能往中间走
    }
    return dp[start][K];
}

int main()
{
    int N = 7, K = 9, cur = 4, aim = 5;
    //暴力递归
    cout << "暴力递归方法 : " << way1(N, K, cur, aim) << endl;
    //记忆搜索
    // cur范围: 1 ~ N    rest范围: 0 ~ K
    vector<vector<int>> dp(N + 1, vector<int>(K + 1, -1)); // dp初始化值全为-1
    cout << "记忆搜索法 : " << way2(N, K, cur, aim, dp) << endl;
    //动态规划
    cout << "记忆搜索法 : " << way3(N, K, cur, aim) << endl;
    return 0;
}

1.2机器人走路问题

给定一个数组arr,玩家每次从左边或右边拿走一个。规定A玩家先拿,B玩家后拿。两个玩家都绝顶聪明,返回游戏获胜者得分。

#include 
#include 
using namespace std;

//暴力递归
//先手函数(暴力)
int fir1(int L, int R, const vector<int> arr)
{
    int aft1(int L, int R, const vector<int> arr);
    if (L == R) //如果最后剩一个值,直接取走
    {
        return arr[L];
    }
    //取走左边的 arr[L] 时,后手最大值为 aft(L+!,R) 取走 arr[R] 时,后手最大值为 aft(L,R-1)
    return max(arr[L] + aft1(L + 1, R, arr), arr[R] + aft1(L, R - 1, arr));
}
//后手函数(暴力)
int aft1(int L, int R, const vector<int> arr)
{
    if (L == R) //只有一个值,后手无值取
    {
        return 0;
    }
    //等对手取完后,去两种先手最优的较小值,因为进入哪个分支由对手确定,所以取最小
    return min(fir1(L + 1, R, arr), fir1(L, R - 1, arr));
}
//暴力递归方法
int way1(vector<int> arr)
{
    int L = 0;                                    //左索引
    int R = arr.size() - 1;                       //右索引
    return max(fir1(L, R, arr), aft1(L, R, arr)); //返回获胜者取得的值的和
}
//***************************************************************************************
//记忆搜索法
//先手函数(记忆搜索)
int fir2(int L, int R, const vector<int> arr, vector<vector<int>> &save_f, vector<vector<int>> &save_a)
{
    int aft2(int L, int R, const vector<int> arr, vector<vector<int>> &save_f, vector<vector<int>> &save_a);
    if (save_f[L][R] != -1)
    {
        return save_f[L][R];
    }
    int ret = 0;
    if (L == R)
    {
        ret = arr[L];
        save_f[L][R] = ret;
        return ret;
    }
    ret = max(arr[L] + aft2(L + 1, R, arr, save_f, save_a), arr[R] + aft2(L, R - 1, arr, save_f, save_a));
    save_f[L][R] = ret;
    return ret;
}

//后手函数(记忆搜索)
int aft2(int L, int R, const vector<int> arr, vector<vector<int>> &save_f, vector<vector<int>> &save_a)
{
    if (save_a[L][R] != -1)
    {
        return save_a[L][R];
    }
    int ret = 0;
    if (L == R)
    {
        save_a[L][R] = 0;
        return 0;
    }
    ret = min(fir2(L + 1, R, arr, save_f, save_a), fir2(L, R - 1, arr, save_f, save_a));
    save_a[L][R] = ret;
    return ret;
}

//记忆搜素法
int way2(vector<int> arr)
{
    int L = 0;
    int R = arr.size() - 1;
    int len = arr.size();                                  //原数组长度
    vector<vector<int>> save_f(len, vector<int>(len, -1)); // fir2函数动态规划表
    vector<vector<int>> save_a(len, vector<int>(len, -1)); // aft2函数动态规划表
    return max(fir2(L, R, arr, save_f, save_a), aft2(L, R, arr, save_f, save_a));
}

//******************************************************************************************

//动态规划法

int way3(vector<int> arr)
{
    int len = arr.size();
    vector<vector<int>> firMap(len, vector<int>(len, 0)); //先手函数的动态规划表(初始值全为0)
    vector<vector<int>> aftMap(len, vector<int>(len, 0)); //后手函数的动态规划表(初始值全为0)
    for (int i = 0; i < len; ++i)
    {
        firMap[i][i] = arr[i]; //先手的 bace case
        // aftMap[i][i] = 0;  后手 bace case 为对角线元素全为 0 ,因为初始化时直接全给的 0 ,所以不用重复赋值
    }
    for (int startCol = 1; startCol < len; ++startCol) // 从第一列开始走斜线,因为对角线已经标过了
    {
        int row = 0;           //遍历行
        int column = startCol; //遍历列
        while (column < len)   //斜着走,当到达最后一列时,这一条斜线到边界
        {
            firMap[row][column] = max(arr[row] + aftMap[row + 1][column], arr[column] + aftMap[row][column - 1]);
            // firMap 取决于 aftMap 镜像位置左方和左下角的值
            aftMap[row][column] = min(firMap[row + 1][column], firMap[row][column - 1]);
            // aftMap 取决于 firMap 镜像位置左方和左下角的值
            ++row; //向右下移动,直到这条斜线遍历完
            ++column;
        }
    }
    return max(firMap[0][len - 1], aftMap[0][len - 1]); //返回先手和后手中最大值
}

//主函数
int main()
{
    vector<int> arr = {5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7};
    cout << "暴力递归法结果: " << way1(arr) << endl;
    cout << "记忆搜素法结果: " << way2(arr) << endl;
    cout << "动态规划法结果: " << way3(arr) << endl;
    return 0;
}

3.总结:

提示:这里填写问题的分析:


你可能感兴趣的:(C++学习记录,c++,动态规划,学习)