【Hello Algorithm】暴力递归到动态规划(一)

暴力递归到动态规划(一)

    • 斐波那契数列的动态规划
    • 机器人走路
      • 初级递归
      • 初级动态规划
      • 动态规划
    • 先后选牌问题
      • 初级递归
      • 初级动态规划
      • 动态规划

我们可以一句话总结下动态规划

动态规划本质是一种以空间换时间的行为 如果你发现有重复调用的过程 在经过一次之后把结果记下来 下次调用的时候直接用 这就是动态规划

斐波那契数列的动态规划

一般来说我们可以使用递归来解决斐波那契数列问题 代码如下

int fib(int n)    
{    if (n <= 0)    {    cerr << "error" << endl;    }    if (n <= 2)    {    return 1;    }    return fib(n-1) + fib(n-2);    
}    

当然 这种方式会产生大量的重复计算 所以说我们可以保存上个和上上个的计算值来进行动态规划

int dpfib(int n)    
{    if (n <= 2)    {    return 1;    }    int i = 1;    int j = 1;    int k = 0;    while (n-2)    {    k = i + j;    i = j;    j = k;     n--;    }    return k;    
}     

这样子写代码就能避免大量的重复计算了

机器人走路

初级递归

假设现在有1~N个位置

在这里插入图片描述

有一个小机器人 现在在START位置

在这里插入图片描述
它现在要去aim位置 (aim为1~N上的随机一点) 能走K步 (K >= 0)

每次只能走一步 不能越界 不能停止 现在请问有多少种方式能走走到

现在假设 S = 2 N = 5 AIM = 4 K = 6

我们一开始写出的递归方程如下

int process1(int cur , int rest , int aim , int k)  

参数含义如下

  • cur 当前位置
  • rest 剩余步数
  • aim 目标地点
  • k 能走的步数

base case为

  • 如果剩余步数为0 则判断cur是否为aim地点

否则我们执行递归继续往下走

int process1(int cur , int rest , int aim , int n)
{                             if (rest == 0){return cur == aim ? 1 : 0;}if (cur == 1){return process1(2 , rest -1 , aim , n);}if (cur == n){return process1(n-1 , rest-1 , aim , n);}                                      return process1(cur-1 , rest-1 , aim , n)+ process1(cur+1 , rest-1 , aim , n);                                                                                       
}  

初级动态规划

现在我们要进行进一步的动态规划

我们想想看在递归函数中有真正决定的是哪两个参数

我们每次传递参数的时候 aimn 是不变的

其实每次变化的就是 currest

在这里插入图片描述

我们可以将该函数往下推演 我们会发现会出现两个相同的结果

如果继续往下展开的话则肯定会有重复的部分 所以说我们最好能将这些函数的结果记录下来 避免重复计算

我们选择使用一个二维数组存储每个函数的计算结果

 vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    for (int i = 0 ; i < n + 1 ; i++ )    {    for (int j = 0; j < rest + 1 ; j++)                                                                                         {    dp[i][j] = -1;    }    }    

这个二维数组 i j 分别标识当前位置和剩余步数

该数组的值表示在当前位置下走剩余步数能走到目的地有多少种解法

我们首先全部初始化为-1

int _process2(int cur , int rest , int aim , int n , vector<vector<int>>& dp)
{if (dp[cur][rest] != -1){return dp[cur][rest];}int ans = 0;if (rest == 0){ans = cur == aim ? 1 : 0;}else if (cur == 1){ans = _process2(2 , rest-1 , aim , n , dp);}else if (cur == n){ans = _process2(n-1 , rest-1 , aim , n , dp);}                                                                                                                             else {ans = _process2(cur -1  , rest -1 , aim , n , dp ) + _process2(cur +1 , rest -1 , aim , n , dp);    }    dp[cur][rest] = ans;return ans;}

之后在我们的函数中 如果数组中有结果 我们就直接返回 如果没有结果我们就将结果记录在数组中后返回 也能得到一样的结果

动态规划

我们以cur为横坐标 rest为纵坐标画一个图 并且将cur为0的时候值填入图中
在这里插入图片描述
当cur为1的时候 我们回顾下我们的代码 我们会发现 此时该格上的数字只依赖于 dp[2][rest-1]

当cur为n的时候 我们回顾下之前的带啊吗 我们会发现 此时该格上的数字只依赖于 dp[n-1][rest-1]

当cur介于两者之间的时候 此时该格上的数字依赖于两种路径

dp[cur-1][rest-1] + dp[cur+1][rest-1]

如下图
在这里插入图片描述

那么我们既然有了第一列的数字 我们就可以推出整个dp数组的数值

从而我们就能得出 当前为start 还有k步要走的时候 我们有几种路径

代码表示如下

int process3(int cur , int rest , int aim , int n)
{vector<vector<int>> dp(n + 1 , vector<int>(rest + 1));    for (int i = 0 ; i < n + 1 ; i++ )    {    for (int j = 0; j < rest + 1 ; j++)    {    dp[i][j] = 0;    }    }    // row 1    dp[aim][0] = 1;    for (int r = 1 ; r <= rest ; r++)    {    dp[1][r] = dp[2][r-1];    for (int c = 2 ; c < n ; c++)                                                                                               {    dp[c][r] = dp[c-1][r-1] + dp[c+1][r-1];    }    dp[n][r] = dp[n-1][r-1];    }    return dp[cur][rest];   
} 

这就是完整的解决动态规划问题的步骤

先后选牌问题

初级递归

假设现在给你一个数组 长度为N (N > 0) 数组内部储存着int类型的值 大小为(1~100)

现在两个人先后选数字 有如下规定

  • 只能选择最边界的数字
  • 如果这个数字被选择了 则从数组中移除

那么我们其实可以写出先选和后选两个函数

一开始我们写出的递归函数如下

int f(vector<int>& arr , int L , int R)  
int g(vector<int>& arr , int L , int R)

这里两个函数的意义分别是

  • f优先选择的最大值
  • g后手选择的最大值

参数的含义是

  • arr 数组
  • L 左边界
  • R 右边界

代码表示如下

int f(vector<int>& arr , int L , int R)    
{                if (L == R)                                                                                                                   {                   return arr[L];    }                                         int p1 = arr[L] + g(arr , L + 1 , R );    int p2 = arr[R] + g(arr , L , R - 1);    return max(p1 , p2);    
}     int g(vector<int>& arr , int L , int R)    
{                  if (L == R)    {                return 0;    }                               int p1 = f(arr , L + 1 , R);int p2 = f(arr , L , R -1);return min(p1 , p2);
}

这里解释下为什么g函数中要取最小值

因为先手的人可能会拿走L或者R 给我们造成p1 或者 p2两种结果

因为先手的人要赢 所以说只可能会给我们最差的一种结果 所以一定会是较小的那个值

初级动态规划

这里变化的参数实际上就只有左边界和右边界

我们就可以围绕着这两个边界来建表

在这里插入图片描述

如果按照函数的依赖关系展开 我们很快就会发现重复项

所以说我们要围绕着重复项建表来达到动态规划的效果

而由于这里有两个函数 f 和 g 所以说 我们要建立两张表

我们以为L为横坐标 R为纵坐标建立两张表

L和R的范围是1 ~ R+1

代码表示如下

int _f2(vector<int>& arr , int L , int R , vector<vector<int>>& fmap , vector<vector<int>>& gmap)    
{                         if (fmap[L][R] != 0)    {    return fmap[L][R];                                                                                                          }    int ans = 0;    if ( L == R)    {    ans = arr[L];    }    else     {    int p1 = arr[L] + _g2(arr , L+1 , R , fmap , gmap);    int p2 = arr[R] + _g2(arr , L , R-1 , fmap , gmap);    ans = max(p1 , p2);    }    fmap[L][R] = ans;    return ans;    } 
int _g2(vector<int>& arr , int L , int R , vector<vector<int>>& fmap , vector<vector<int>>& gmap)
{                     if (gmap[L][R] != 0){return gmap[L][R];}int ans = 0;if (L == R){    ans = 0;}                                            else                                          {                    int p1 = _f2(arr , L , R -1 , fmap , gmap);int p2 = _f2(arr , L + 1 , R , fmap , gmap);ans = min(p1 , p2);}          gmap[L][R] = ans;return ans;
}

动态规划

我们以L为横坐标 R为纵坐标画一个gmap图 并且将L == R的时候的值填入图中

在这里插入图片描述

我们可以发现该图的左下角我们是不需要的因为L不可能大于R

那么我们的gmap上右上角的随机一点是依赖于什么呢?

回归到我们最初的递归方程中

_f2(arr , L , R -1 , fmap , gmap);
_f2(arr , L + 1 , R , fmap , gmap);

我们可以发现是依赖于fmap的 L R-1 以及 L+1 R
在这里插入图片描述

如果映射到fmap中 我们可以发现刚好是斜对角线上的两点 既然我们现在知道了斜对角线上的值 我们现在就可以开始填写这两张map表了

代码表示如下

int f3(vector<int>& arr , int L , int R)
{vector<vector<int>> fmap( R + 1, vector<int>(R+1));for (int i = 0 ; i < R + 1 ; i++){for (int j = 0; j < R + 1 ; j++){fmap[i][j] = 0;if (i == j){fmap[i][j] = arr[i];}}}vector<vector<int>> gmap( R+1 , vector<int>(R+1));for (int i = 0 ; i < R + 1 ; i++){for (int j = 0; j < R + 1 ; j++){gmap[i][j] = 0;}}                                                                                                                             for (int startcol = 1 ; startcol < R + 1; startcol++ ){int col = startcol;int row = 0;while (col < R + 1){fmap[row][col] = max(gmap[row][col-1] + arr[row] , gmap[row+1][col] + arr[col]);gmap[row][col] = min(fmap[row][col-1] , fmap[row+1][col]);row++;col++;}}return fmap[L][R];
}

其实最关键的代码就是这两行

  fmap[row][col] = max(gmap[row][col-1] + arr[col] , gmap[row+1][col] + arr[row]);gmap[row][col] = min(fmap[row][col-1] , fmap[row+1][col]);

我们可以发现 这其实就是将原来代码中的函数

    int p1 = arr[L] + _g2(arr , L+1 , R , fmap , gmap);    int p2 = arr[R] + _g2(arr , L , R-1 , fmap , gmap);    ans = max(p1 , p2);    

变成了表中的数字相加

这就是动态规划的一般套路

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/157468.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GRU的 电影评论情感分析 - python 深度学习 情感分类 计算机竞赛

1 前言 &#x1f525;学长分享优质竞赛项目&#xff0c;今天要分享的是 &#x1f6a9; GRU的 电影评论情感分析 - python 深度学习 情感分类 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 这…

git常用命令和开发常用场景

git命令 git init 创建一个空的git仓库或者重新初始化已有仓库 git clone [url] 将存储库克隆到新目录 git add 添加内容到索引 git status 显示工作树状态 git commit -m "" 记录仓库的修改 git reset 重置当前HEAD到指定的状态 git reset –-soft&#xff1a;…

【Java学习之道】Java常用集合框架

引言 在Java中&#xff0c;集合框架是一个非常重要的概念。它提供了一种方式&#xff0c;让你可以方便地存储和操作数据。Java中的集合框架包括各种集合类和接口&#xff0c;这些类和接口提供了不同的功能和特性。通过学习和掌握Java的集合框架&#xff0c;你可以更好地管理和…

Response Status Code 301、302

目录 Information Django redirect Influence Information HTTP状态码301、302和304分别表示以下情况&#xff1a; codeinformation301&#xff08;Moved Permanently&#xff09; 永久重定向。当请求的资源已经被永久地移动到了一个新的URI时&#xff0c;服务器会返回这个…

清洁洗鞋商城小程序的作用是什么

人靠衣装&#xff0c;一身干净合身的衣物总是给人赏心悦目的感觉&#xff0c;人们对颜值要求越来越高&#xff0c;不仅是衣服&#xff0c;鞋也是重要的组成部分。各种品牌样式鞋&#xff0c;很多人家里往往有几十双&#xff0c;而在清洁这一块&#xff0c;没有时间、或材质特殊…

c++视觉处理---直方图均衡化

直方图均衡化 直方图均衡化是一种用于增强图像对比度的图像处理技术。它通过重新分布图像的像素值&#xff0c;以使图像的直方图变得更均匀&#xff0c;从而提高图像的视觉质量。在OpenCV中&#xff0c;您可以使用 cv::equalizeHist 函数来执行直方图均衡化。以下是 cv::equal…

【ARM CoreLink 系列 7 -- TZC-400控制器简介】

文章目录 背景介绍1.1 TZC-400 简介1.2 TZC-400 使用示例1.3 TZC-400 interfaces1.3.1 FPID1.3.2 NSAID Regionregion 检查规则 1.4 Features1.5 Register summary1.6 TZC-400和TZPC和TZASC区别 背景介绍 为了确保内存能够正确识别总线的信号控制位&#xff0c;新增一个TrustZ…

2、TCP协议基础

TCP协议基础 1、3次握手建立连接 SYN表示建立连接的标志位&#xff0c;ACK为应答标志位 #mermaid-svg-v9bU5HHw4lMWPKc7 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-v9bU5HHw4lMWPKc7 .error-icon{fill:#55222…

计算机竞赛python区块链实现 - proof of work工作量证明共识算法

文章目录 0 前言1 区块链基础1.1 比特币内部结构1.2 实现的区块链数据结构1.3 注意点1.4 区块链的核心-工作量证明算法1.4.1 拜占庭将军问题1.4.2 解决办法1.4.3 代码实现 2 快速实现一个区块链2.1 什么是区块链2.2 一个完整的快包含什么2.3 什么是挖矿2.4 工作量证明算法&…

Java 基于SpringBoot的某家乡美食系统

1 简介 《Java 基于SpringBoot的某家乡美食系统》该项目含有源码、文档等资料、配套开发软件、软件安装教程等。系统功能完整&#xff0c;适合作为毕业设计、课程设计、数据库大作业学习使用。 功能介绍 这个项目是基于 SpringBoot和 Vue 开发的地方美食系统&#xff0c;包括…

竞赛选题 深度学习 机器视觉 人脸识别系统 - opencv python

文章目录 0 前言1 机器学习-人脸识别过程人脸检测人脸对其人脸特征向量化人脸识别 2 深度学习-人脸识别过程人脸检测人脸识别Metric Larning 3 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 人脸识别系统 该项目…

小谈设计模式(25)—职责链模式

小谈设计模式&#xff08;25&#xff09;—职责链模式 专栏介绍专栏地址专栏介绍 职责链模式分析角色分析抽象处理者&#xff08;Handler&#xff09;具体处理者&#xff08;ConcreteHandler&#xff09;客户端&#xff08;Client&#xff09; 优缺点分析优点123 缺点12 应用场…

当下测试行业中UI自动化面临的难点及如何解决

经常有人会问&#xff0c;什么样的项目才适合进行UI自动化测试呢&#xff1f;UI自动化测试相当于模拟手工测试&#xff0c;通过程序去操作页面上的控件。而在实际测试过程中&#xff0c;经常会遇到无法找到控件&#xff0c;或者因控件定义变更而带来的维护成本等问题。 哪些场…

深度学习基础知识 给模型的不同层 设置不同学习率

深度学习基础知识 给模型的不同层 设置不同学习率 1、使用预训练模型时&#xff0c;可能需要将2、学习率设置方式&#xff1a; 1、使用预训练模型时&#xff0c;可能需要将 &#xff08;1&#xff09;预训练好的 backbone 的 参数学习率设置为较小值&#xff0c; &#xff08;2…

Fastjson历史版本记录

1.2.24 TemplatesImpl&#xff0c;利用条件苛刻&#xff0c;需要开启Feature.SupportNonPublicField {"type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": ["yv66vgAAADQA...CJAAk"],"_name…

LeetCode刷题总结 - LeetCode 热题 100 - 持续更新

LeetCode 热题 100 其他系列哈希1. 两数之和49. 字母异位词分组128. 最长连续序列 双指针27. 移除元素283. 移动零11. 盛最多水的容器剑指 Offer II 007. 数组中和为 0 的三个数42. 接雨水 滑动窗口438. 找到字符串中所有字母异位词3. 无重复字符的最长子串 字串560. 和为 K 的…

PerformanceRunner国产化性能测试工具

国产化性能测试工具PerformanceRunner&#xff08;简称PR&#xff09;通过模拟海量用户并发测试整个系统的承受能力&#xff0c;实现压力测试、性能测试、配置测试、峰值测试等。大限度地缩短测试时间&#xff0c;优化性能和加速应用系统的发布周期。 泽众PR性能测试工具是国内…

基于Linux上MySQL8.*版本的安装-参考官网

本地hadoop环境安装好,并安装好mysql mysql下载地址及选择包 MySQL :: Download MyS的QL Community Server (Archived Versions) mysql安装步骤 下载与上传解压给权限 #mysql安装包上传到/opt下 cd /usr/local/ #解压到此目录 tar -xvf /opt/mysql-8.0.33-linux-glibc2.12-…

C# 使用 RSA 加密算法生成证书签名产生“The system cannot find the file specified”异常

使用 C# 中 RSA&#xff08;System.Security.Cryptography.RSA&#xff09; 加密算法生成证书签名进行身份验证&#xff0c;在 VS2022 开发工具本地运行应用程序一切正常。 但将应用程序部署到远程服务器&#xff08;如&#xff1a;Azure App Services&#xff09;&#xff0c…

基于PLC的机械手控制系统设计

目录 摘 要......................................................................................................................... 1 第一章 绪论.............................................................................................................…