【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.mzph.cn/news/103840.shtml

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

相关文章

windows频繁更新问题解决方案

解决方案&#xff1a;将更新策略增加到无穷大 1.windowsr 输入regedit 2.找到&#xff1a;HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 3.右键新建DWORD32 4.命名&#xff1a;FlightSettingsMaxPauseDays 5.双击&#xff1a;数值数据改为4321 基数&#…

Spring是什么?为什么要使用Spring?

目录 前言 一、Spring是什么&#xff1f; 1.1 轻量级 1.2 JavaEE的解决方案 二、为什么要使用Spring 2.1 传统方式完成业务逻辑 2.2 使用Spring模式完成业务逻辑 三、为什么使用Spring&#xff1f; 前言 本文主要介绍Spring是什么&#xff0c;并且解释为何要去使用Spring&…

vite + vu3 + ts 项目,npm run build 报错

新建了vite 项目&#xff0c;build的时候报错&#xff1a; npm ERR! demo10.0.0 build: vue-tsc && vite build npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the demo10.0.0 build script.npm ERR! This is probably not a problem with npm. There is like…

【Vue Router 3】入门

简介 Vue Router让SPA&#xff08;Single-page Application&#xff09;的构建更加容易。 Vue Router的功能&#xff1a; 嵌套的路由/视图映射模块化的、基于组件的router配置route params, query, wildcards由Vue过渡系统支持的视图过渡效果细粒度&#xff08;fine-grained…

c语言练习87:合并两个有序数组

合并两个有序数组 合并两个有序数组https://leetcode.cn/problems/merge-sorted-array/ 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff…

动态资源平衡:主流虚拟化 DRS 机制分析与 SmartX 超融合的实现优化

资源的动态调度是虚拟化软件&#xff08;或超融合软件&#xff09;中的一项重要功能&#xff0c;主要指在虚拟化集群中&#xff0c;通过动态改变虚拟机的分布&#xff0c;达到优化集群可用性的目标。这一功能以 VMware vSphere 发布的 Distributed Resource Scheduler&#xff…

day62:ARMday9,I2c总线通信

作业&#xff1a;按键中断实现LED1、蜂鸣器、风扇 key_in.c: #include "key_in.h"void gpio_init() {//RCC使能//GPIOERCC->MP_AHB4ENSETR | (0x1<<4);//GPIOBRCC->MP_AHB4ENSETR | (0x1<<1);//PE10、PB6、PE9输出模式GPIOE->MODER & ~(0…

AutoGPT:让 AI 帮你完成任务事情 | 开源日报 No.54

Significant-Gravitas/AutoGPT Stars: 150.4k License: MIT AutoGPT 是开源 AI 代理生态系统的核心工具包。它采用模块化和可扩展的框架&#xff0c;使您能够专注于以下方面&#xff1a; 构建 - 为惊人之作打下基础。测试 - 将您的代理调整到完美状态。查看 - 观察进展成果呈…

基于SpringBoot的网上订餐系统

基于SpringBoot的网上订餐系统的设计与实现 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBootMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;用户、管理员管理员&#xff1a;登录、个人中心、会员管理、…

《DevOps 精要:业务视角》- 读书笔记(六)

DevOps 精要:业务视角&#xff08;六&#xff09; 第6章 结语 第6章 结语 DevOps有自己的起源以及存在的前提。到2010年&#xff0c;随着条件成熟&#xff0c;形成了对信息科技中开发与运维进行管理的需求以及可能性。这引发了DevOps运动的兴起。 正如众多布道师经常提及的&a…

大数据分析实践 | 过滤和抽样

文章目录 &#x1f4da;相关函数&#x1f407;数据读取和写入&#xff1a;read_csv和to_csv&#x1f407;数据过滤&#xff1a;pandas.DataFrame.loc&#x1f407;抽样&#xff1a;pandas.DataFrame.sample&#x1f407;删除缺失数据&#xff1a;dropha&#x1f407;添加元素&a…

TensorFlow入门(十二、分布式训练)

1、按照并行方式来分 ①模型并行 假设我们有n张GPU,不同的GPU被输入相同的数据,运行同一个模型的不同部分。 在实际训练过程中,如果遇到模型非常庞大,一张GPU不够存储的情况,可以使用模型并行的分布式训练,把模型的不同部分交给不同的GPU负责。这种方式存在一定的弊端:①这种方…

sklearn处理离散变量的问题——以决策树为例

最近做项目遇到的数据集中&#xff0c;有许多高维类别特征。catboost是可以直接指定categorical_columns的【直接进行ordered TS编码】&#xff0c;但是XGboost和随机森林甚至决策树都没有这个接口。但是在学习决策树的时候&#xff08;无论是ID3、C4.5还是CART&#xff09;&am…

嵌入式养成计划-40----C++菱形继承--虚继承--多态--模板--异常

九十四、菱形继承 94.1 概念 菱形继承又称为钻石继承&#xff0c;是由公共基类派生出多个中间子类&#xff0c;又由中间子类共同派生出汇聚子类&#xff0c;汇聚子类会得到多份中间子类从公共基类继承下来的数据成员&#xff0c;会造成空间浪费&#xff0c;没有必要。 所以存…

工程师必须记住的电路元件符号及英语翻译

很多电子小白第一次接触印刷电路板&#xff08;PCB&#xff09;时&#xff0c;总会头痛那些密密麻麻的元件字母符号&#xff0c;这些电路元件符号基本上都是采用英语缩写&#xff0c;下面我们来看看这些电路元件的英语符号有哪些&#xff1f; 电阻器&#xff08;Resistor&#…

C++入门指南:类和对象总结友元类笔记(下)

C入门指南:类和对象总结友元类笔记&#xff08;下&#xff09; 一、深度剖析构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字 二、static成员2.1 概念2.2 特性 三、友元3.1 友元函数3.2 友元类 四、 内部类4.1 概念4.2 特征 五、拷贝对象时的一些编译器优化六、深…

Linux进阶-加深进程印象

目录 进程 进程状态转换 进程状态 启动新进程 system()函数 system.c文件 Makefile文件 执行过程 fork()函数 函数原型 fork.c文件 Makefile文件 执行过程 exec系列函数 函数原型 execl.c文件 Makrfile文件 执行过程 终止进程 exit()函数和_exit()函数 头…

机器人制作开源方案 | 杠杆式6轮爬楼机器人

1. 功能描述 本文示例将实现R281b样机杠杆式6轮爬楼机器人爬楼梯的功能&#xff08;注意&#xff1a;演示视频中为了增加轮胎的抓地力&#xff0c;在轮胎上贴了双面胶&#xff0c;请大家留意&#xff09;。 2. 结构说明 杠杆式6轮爬楼机器人是一种专门用于爬升楼梯或不平坦地面…

【elasticsearch】elasticsearch8.0.1使用rpm包安装并启用TLS

背景 公司的业务需要在加密的情况下使用&#xff0c;为此&#xff0c;研究测试了一下es8是如何启用TLS的。以下是测试使用过程。 x-pack了解 在 Elasticsearch 7.11.0 版本及更高版本中&#xff0c;X-Pack 功能在默认情况下已经整合到 Elastic Stack 的各个组件中&#xff0…

Element-UI的使用——表格el-table组件去除边框、滚动条设置、隔行变色、去除鼠标悬停变色效果(基于less)

// Element-ui table表格去掉所有边框,如下&#xff1a; // 备注&#xff1a;若去掉所有边框&#xff0c;可自行将头部边框注释掉即可 // 该样式写在style scoped外面在el-table 中添加class"customer-table"类名 //去掉每行的下边框/deep/ .el-table td.el-table__c…