由于STL库更方便,本文用的是vector,没有用数组。

文中程序可以直接运行,可当做模板进行修改。

目录

一、动态规划算法思想

二、动态规划处理一维问题(以走台阶为例)

三、动态规划处理二维问题(以从矩阵左上角走到右下角最短路径问题为例)

四、动态规划求子序列(以求最长严格递增子序列长度为例)

五、最长公共子序列的长度

六、输出最长公共子序列


一、动态规划算法思想

动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。具体的动态规划算法多种多样,但它们具有相同的填表格式。

分治法核心思想: 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题

动态规划法思想:自底向上,推导递推公式,避免重复计算,降低计算量

二、动态规划处理一维问题(以走台阶为例)

问题描述:有10级台阶,一个人每次上一级或者两级,问有多少种走完10级台阶的方法。

解法:

因为问的是一共有多少种走法,则走到第n个台阶时的总走法应该是在第n-1级台阶时的总走法(再走一步一级跨越)加上在第n-2级台阶时的总走法(再走一步二级跨越),这里从上一步到第n台阶为什么不加1呢?因为从n-1台阶或者n-2台阶到n台阶就是一种走法。

1.设数组dp:每个位置存的值代表该台阶位置的总走法。(注意数组是从0开始的,即dp[0]代表台阶1)

2.分析边界条件:因为每次可以上一级或者两级,所以边界分析时需要考虑到两级台阶。当台阶数为1时走法为1(即走一级即毕),台阶数为2时走法为2(走两次一级和走一次二级)。

即:dp[0] = 1;    dp[1] = 2;

3.分析递归关系:对于任一台阶都可以分为通过两级或者一级到达。

即:dp[i] = dp[i - 1] + dp[i - 2];

4.遍历台阶:遍历台阶,数组的每个数值代表的是到该位置的总的走法,则数组最后一个位置的值就是总的走法。

#include <iostream>
#include<vector>
#include<string>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <algorithm>//算法头文件
#include <numeric>
#include <stack>
#include<typeinfo>using namespace std;int getSteps(int n) {if (n < 1) return 0;if (n == 1) return 1;if (n == 2) return 2;vector<int> dp(n);dp[0] = 1;dp[1] = 2;for (int i = 2; i < n; i++) {dp[i] = dp[i - 1] + dp[i - 2];}return dp[n - 1];
}int main()
{cout << getSteps(10) << endl;return 0;
}

三、动态规划处理二维问题(以从矩阵左上角走到右下角最短路径问题为例)

问题描述:给定一个矩阵m,从左上角开始每次只能向右走或者向下走,最后达到右下角的位置,路径中所有数字累加起来就是路径和,返回所有路径的最小路径和,如果给定的m如下,那么路径1,3,1,0,6,1,0就是最小路径和,返回11.

矩阵从左上角走到右下角
  0 1 2 3 4
0 0 0 0 0 0
1 0 1 3 5 9
2 0 8 1 3 5
3 0 5 0 6 1
4 0 8 8 4 0

解法:

对于矩阵中的每一个位置,由于要到达当前位置的方式只能是从左边或者是上边。所以当前位置的最小路径和应该是该位置左边或者上边中的路径和较小者加上当前位置的路径

1.设置数组:因为是二维矩阵,所以要设置二维数组,每个元素代表对应位置的路径和。

2.边界分析:因为当前位置只能通过其左边位置和上边位置达到,则最左边的一列(第1列)只能由上边位置到达;最上边行(第1行)只能由左边位置到达。故初始化边界条件:

 dp[0][0] = m[0][0];//初始化第一列for (int i = 1; i < row; i++) dp[i][0] = dp[i - 1][0] + m[i][0];//初始化第一行for (int i = 1; i < col; i++)dp[0][i] = dp[0][i - 1] + m[0][i];

3.递推关系:对于每一个位置,可由其左边或上边的位置达到。

dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];

4.遍历矩阵:矩阵每个位置的数值代表的就是到该位置的最小路径和,则右下角的值就是最终的结果。

#include <iostream>
#include<vector>
#include<string>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <algorithm>//算法头文件
#include <numeric>
#include <stack>
#include<typeinfo>using namespace std;int const x_length = 5, y_length = 5;
vector<vector<int>> m = {{0, 0, 0, 0, 0},{0, 1, 3, 5, 9},{0, 8, 1, 3, 5},{0, 5, 0, 6, 1},{0, 8, 8, 4, 0}
};int getSteps(vector<vector<int>> &m) {if (m.empty()) return 0;int row = m.size();int col = m[0].size();vector<vector<int>> dp(row, vector<int>(col));/******边界初始化******/dp[0][0] = m[0][0];//初始化第一列for (int i = 1; i < row; i++) dp[i][0] = dp[i - 1][0] + m[i][0];//初始化第一行for (int i = 1; i < col; i++)dp[0][i] = dp[0][i - 1] + m[0][i];/******递推关系********/for (int i = 1; i < row; i++) {for (int j = 1; j < col; j++) {dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];}}return dp[row - 1][col - 1];
}
int main()
{cout << getSteps(m) << endl;return 0;
}

四、动态规划求子序列(以求最长严格递增子序列长度为例)

问题描述:

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列。例如,nums = [10,9,2,5,3,7,101,18] 最长递增子序列是 [2,3,7,101],因此长度为 4 。

这里的「上升」是「严格上升」,例如: [2, 3, 3, 6, 7] 这样的子序列是不符合要求的。

题目只问最长上升子序列的长度,没有问最长上升子序列是什么,因此考虑使用动态规划。

第 1 步:状态定义。dp[i] 表示以 nums[i] 结尾的最长上升子序列的长度。即:在 [0, ..., i] 的范围内,选择以数字 nums[i] 结尾可以获得的最长上升子序列的长度。

说明:以 nums[i] 结尾,是子序列动态规划问题的经典设计状态思路,思想是动态规划的无后效性(定义得越具体,状态转移方程越好推导)。

第 2 步:推导状态转移方程:遍历到 nums[i] 的时候,我们应该把下标区间 [0, ... ,i - 1] 的 dp 值都看一遍,如果当前的数 nums[i] 大于之前的某个数,那么 nums[i] 就可以接在这个数后面形成一个更长的上升子序列。把前面的数都看了, dp[i] 就是它们的最大值加1。即比当前数要小的那些里头,找最大的,然后加 1。

状态转移方程即:dp[i] = max(1 + dp[j] if j < i and nums[j] < nums[i])

第 3 步:初始化。单独一个数是子序列,初始化的值为 1;

第 4 步:输出。应该扫描这个 dp 数组,其中最大值的就是题目要求的最长上升子序列的长度。

#include <iostream>
#include<vector>
#include<string>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <algorithm>//算法头文件
#include <numeric>
#include <stack>
#include<typeinfo>using namespace std;vector<int> nums = { 10,9,2,5,3,7,101,18};int getSteps(vector<int> nums) {int len = nums.size();if (len == 0) return 0;if (len < 2) return  nums[0];vector<int> dp(len, 1);//已经包含了初始边界条件for (int i = 1; i < len; i++) {int tmp = INT_MIN;for (int j = 0; j < i; j++) {if (nums[i] > nums[j])dp[i] = max(dp[j] + 1, dp[i]);}}return *max_element(dp.begin(), dp.end());
}
int main()
{cout << getSteps(nums) << endl;return 0;
}

五、最长公共子序列的长度

题目描述:

给定两个字符串str1和str2,返回两个字符串的最长公共子序列,例如:str1="1A2C3D4B56",str2="B1D23CA45B6A","123456"和"12C4B6"都是最长公共子序列,返回哪一个的长度都行。

做这种题,我们要用一个二维数组(dp[MAX_N][MAX_N])来存放每一个状态的值。那么,每一个网格的值是怎么来的呢。在这里我们把每一个状态即dp[i][j] 看做最长公共子序列的长度。由此我们,s1 … s(i+1) 和 t1 … t(j+1) 对应的公共子列长度可能是:

当s(i+1) == t(j+1),在 s1 … si 和 t1 … tj 的公共子列末尾追加上s(i+1) 。

否则则可能是 s1 … si 和 t1 … t(j+1) 的公共子列或者 s1 … s(i+1) 和 t1 … tj 的公共子列最大值。

对应以下一个公式:

#include <iostream>
#include<vector>
#include<string>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <algorithm>//算法头文件
#include <numeric>
#include <stack>
#include<typeinfo>using namespace std;vector<int> nums = { 10,9,2,5,3,7,101,18};
string str1 = "asdf";
string str2 = "adfsd";int getSteps(string &str1, string &str2) {int len1 = str1.size();int len2 = str2.size();vector<vector<int>> dp(len1 + 1, vector<int>(len2 + 1, 0));for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {if (str2[j] == str1[i] )dp[i+1][j+1] = dp[i][j] + 1;elsedp[i + 1][j + 1] = max(dp[i + 1][j], dp[i][j + 1]);}}return dp[len1][len2];}
int main()
{cout << getSteps(str1, str2) << endl;return 0;
}

六、输出最长公共子序列

#include <string>
#include <iostream>
#ifndef MAX
#define MAX(X,Y) ((X>=Y)? X:Y)
#endif
using namespace std;
int **Lcs_length(string X, string Y, int **B)
{int x_len = X.length();int y_len = Y.length();int **C = new int *[x_len + 1];for (int i = 0; i <= x_len; i++){C[i] = new int[y_len + 1];        //定义一个存放最优解的值的表;}for (int i = 0; i <= x_len; i++){C[i][0] = 0;B[i][0] = -2;                     //-2表示没有方向}for (int j = 0; j <= y_len; j++){C[0][j] = 0;B[0][j] = -2;}for (int i = 1; i <= x_len; i++){for (int j = 1; j <= y_len; j++){if (X[i - 1] == Y[j - 1]){C[i][j] = C[i - 1][j - 1] + 1;B[i][j] = 0;             //0表示斜向左上}else{if (C[i - 1][j] >= C[i][j - 1]){C[i][j] = C[i - 1][j];B[i][j] = -1;       //-1表示竖直向上;}else{C[i][j] = C[i][j - 1];B[i][j] = 1;        //1表示横向左}}}}return C;
}void OutPutLCS(int **B, string X, int str1_len, int str2_len)
{if (str1_len == 0 || str2_len == 0){return;}if (B[str1_len][str2_len] == 0)   //箭头斜向左上{OutPutLCS(B, X, str1_len - 1, str2_len - 1);cout << X[str1_len - 1] << endl;}else if (B[str1_len][str2_len] == -1){OutPutLCS(B, X, str1_len - 1, str2_len);}else{OutPutLCS(B, X, str1_len, str2_len - 1);}
}int main()
{string X = "1A2C3D4B56";string Y = "B1D23CA45B6A";int x_len = X.length();int y_len = Y.length();int **C;//定义一个二维数组int **B = new int *[x_len + 1];for (int i = 0; i <= x_len; i++){B[i] = new int[y_len + 1];}C = Lcs_length(X, Y, B);for (int i = 0; i <= x_len; i++){for (int j = 0; j <= y_len; j++){cout << C[i][j] << " ";}cout << endl;}cout << endl;for (int i = 0; i <= x_len; i++){for (int j = 0; j <= y_len; j++){cout << B[i][j] << " ";}cout << endl;}OutPutLCS(B, X, x_len, y_len);//构造最优解system("pause");return 0;
}

详解动态规划法(包含完整可用的代码实例)相关推荐

  1. OpenCV-Python实战(14)——人脸检测详解(仅需6行代码学会4种人脸检测方法)

    OpenCV-Python实战(14)--人脸检测详解(仅需6行代码学会4种人脸检测方法) 0. 前言 1. 人脸处理简介 2. 安装人脸处理相关库 2.1 安装 dlib 2.2 安装 face_r ...

  2. [Pytorch系列-61]:循环神经网络 - 中文新闻文本分类详解-3-CNN网络训练与评估代码详解

    作者主页(文火冰糖的硅基工坊):文火冰糖(王文兵)的博客_文火冰糖的硅基工坊_CSDN博客 本文网址:https://blog.csdn.net/HiWangWenBing/article/detai ...

  3. java 代码块_详解java中的四种代码块

    在java中用{}括起来的称为代码块,代码块可分为以下四种: 一.简介 1.普通代码块: 类中方法的方法体 2.构造代码块: 构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行. ...

  4. matlab 按字母排序,matlab命令大全(按字母排序) 总汇详解最新发布完整珍藏版

    matlab命令大全(按字母排序) 总汇详解最新发布完整珍藏版 abs 绝对值.模.字符的ASCII码值 acos 反余弦 acosh 反双曲余弦 acot 反余切 acoth 反双曲余切 acsc ...

  5. Arduino :PWM详解和电路搭建以及示例代码

    Arduino :PWM详解和电路搭建以及示例代码 PWM 调制介绍 脉冲宽度调制是PWM的全称.它是数字编码的模拟信号电平.由于计算机不能输出模拟电压而只有0或5V数字电压值,我们可以应用调制方波占 ...

  6. 二、SSM整合按步骤详解(清晰的思路加代码)从零开始一步步整合【二】(完结)

    接上一章节继续SSM整合按步骤详解(清晰的思路加代码)从零开始一步步整合[一] 上面讲了Spring和SpringMVC的整合,现在开始下一步,先来搭建一下MyBatis的环境 我们先来看看目录结构图 ...

  7. foreach php,详解PHP中foreach的用法和实例

    本篇文章介绍了详解PHP中foreach的用法和实例,详细介绍了foreach的用法,感兴趣的小伙伴们可以参考一下. 在PHP中经常会用到foreach的使用,而要用到foreach,就必须用到数组. ...

  8. 基于 SurfaceView 详解 android 幸运大转盘,附带实例app

    基于 SurfaceView 详解 android 幸运大转盘,附带实例app 首先说一下,幸运大转盘,以及SurfaceView是在看了也为大神的博客,才有了比较深刻的理解,当然这里附上这位大神的博 ...

  9. 详解在QT中写控制台程序 实例

    详解在QT中写控制台程序 实例 在QT中写控制台程序 实例是本文介绍的内容,不多说了,先来看本文内容. AD: 本文介绍的是详解在QT中写控制台程序 实例,来看那内容.找到两种方法可以写控制台程序 第 ...

最新文章

  1. xml python2.6_如何使用前缀选项解析python 2.6中的参数为-f file.xml
  2. android tible控件_android自定义表单,表格控件TableRowTextView
  3. 服务器之后加码存储,浪潮信息重磅发布新一代 G6 存储平台
  4. Java static , final和常量设计
  5. 构建websocket服务
  6. 论发SCI论文和生孩子的共同点:那我这篇怀的也太久了!
  7. python入门之正则表达式
  8. Linux笔记6_vim编辑器常用命令总结
  9. 大数据技术与应用解读及案例分析(PPT)
  10. c语言万年历查询程序代码,C语言实现万年历程序
  11. Ubuntu18.04安装CUDA10、CUDNN
  12. vue3 + router-view + keepalive parentComponent.ctx.deactivate is not a function
  13. 不占广告位增加网站收入揭秘
  14. Oracle练习题(三)
  15. NGUI 制作字体集和图集
  16. 重磅!中科院院士,任复旦大学新校长!
  17. 学会JavaScript函数式编程(第3部分)
  18. 史上最全的CSS样式实现,提升你的效率
  19. 召集各位软件开发大佬
  20. CentOS 7 磁盘挂载教程

热门文章

  1. 集成电路相关电子书3
  2. 安装VASP5.4.4编译 libfftw3xf_intel.a 碰到的:ICC COMMAND NOT FOUND问题
  3. 18讲.13.3已知f(x,y)=(xy+xy^2)e^(x+y),则偏导[d^10f]/{dx^5*dy^5}
  4. 学习第一行代码coolweather项目第二阶段的开发工作遇到的瓶颈
  5. 低代码 前端页面可视化搭建
  6. 分享一些免费调用的API接口
  7. java课程设计抽奖程序源码_java课程设计---个人博客 彩票抽奖程序 201821123098 钟海清...
  8. 浏览器设置了打开会显示特定网页为什么还是显示2345_浏览器弹窗广告多?阻止网页弹出广告的三种方法...
  9. ARP与RARP 协议
  10. 正确理解实例方法、类方法、静态方法