动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。
实际上是对暴力递归的一种优化,把已经计算过的信息保存起来,下次要用时不用重复计算,并且通过求解动态数组的方式代替了函数的递归调用。
第一步优化在于对计算结果的存储,避免重复计算。可以优化成下面描述的记忆化搜索方法。
第二步优化在于对递归函数调用的优化,直接从bace case 反推,而不是从上往下调用。
机器人在有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;
}
给定一个数组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;
}
提示:这里填写问题的分析: