当前位置: 首页 > news >正文

数据结构与算法(二)动态规划(Java)

目录

    • 一、简介
      • 1.1 什么是动态规划?
      • 1.2 动态规划的两种形式
        • 1)自顶向下的备忘录法(记忆化搜索法)
        • 2)自底向上的动态规划
        • 3)两种方法对比
      • 1.3 动态规划的 3 大步骤
    • 二、小试牛刀:钢条切割
      • 2.1 题目描述
      • 2.2 题目解析
        • 1)第一步:定义数组元素的含义
        • 2)第二步:找出数组元素之间的关系
        • 3)第三步:找出初始值
      • 2.3 最优子结构
      • 2.4 代码实现
        • 1)递归版本
        • 2)备忘录版本
        • 3)自底向上的动态规划

一、简介

1.1 什么是动态规划?

在说明动态规划前,我们先来了解一个小场景:

A: "1+1+1+1+1+1+1+1"A: "上面等式的值是多少?"
B: "(计算...)" "8!"A: "在上面等式的左边写上 '1+',此时等式的值为多少?"
B: "(立刻回答)" "9!"
A: "你这次怎么这么快就知道答案了"
B: "只要在8的基础上加1就行了"

由上面的小故事可知,动态规划 就是 通过记住历史的求解结果来节省时间

1.2 动态规划的两种形式

示例:斐波那契数列,又称黄金分割数列,其数值为:1、1、2、3、5、8、13、21、34,递推公式为:
F ( 0 ) = 1 , F ( 1 ) = 1 , F ( n ) = F ( n − 1 ) + F ( n − 2 ) , n > 2 , n ∈ N ∗ F(0)=1,F(1)=1, F(n)=F(n-1)+F(n-2),n>2,n∈N^{*} F(0)=1,F(1)=1,F(n)=F(n1)+F(n2),n>2,nN
这个算法用递归来实现非常简单,代码如下:

public int fib(int n) {if (n < 2) {return 1;}return fib(n - 1) + fib(n - 2);
}

先来分析一下递归算法的执行流程,假如输入 6,那么执行的递归树如下:

在这里插入图片描述

我们可以发现:

  • 上面的递归树中,每一个结点都会执行一次;
  • 很多结点被重复执行

为了避免这种情况,我们可以把执行过的结点值保存下来,后面用到直接查表,这样可以节省大量时间。

下面看下保存历史记录的两种形式:自顶向下的备忘录法自底向上的动态规划

1)自顶向下的备忘录法(记忆化搜索法)

备忘录法,也叫记忆化搜索法,是比较好理解的:

  • 创建了一个 n+1 大小的数组来保存求出斐波那契数列中的每一个值;
  • 在递归的时候,如果发现之前已经算过了就不再计算;
  • 如果之前没有计算,则计算后放入历史记录中。
public static void main(String[] args) {int n = 6;// 声明数组,用于记录历史,初始化为-1int[] his = new int[n + 1];Arrays.fill(his, -1);System.out.println(fib(n, his));
}public static int fib(int n, int[] his) {if (n < 2) {return 1;}// 读取历史if (his[n] != -1) {return his[n];}int result = fib(n - 1, his) + fib(n - 2, his);// 记录历史his[n] = result;return result;
}
2)自底向上的动态规划

备忘录法还是利用了递归,不管怎样,当计算 fib(6) 的时候还是要去先计算出 fib(1) ~ fib(5),那么为何不先计算出 f(1) ~ f(5) 呢?这就是动态规划的核心:先计算子问题,再由子问题计算父问题

public static int fib(int n) {int[] arr = new int[n + 1];arr[0] = 1;arr[1] = 1;for (int i = 2; i <= n; i++) {arr[i] = arr[i - 2] + arr[i - 1];}return arr[n];
}

自底向上的动态规划方法也是利用数组保存了计算的值,为后面的计算使用。

内存空间优化:

我们观察上面的代码会发现:参与循环的只有 fib(i)fib(i-1)fib(i-2) 项,因此该方法的空间可以进一步的压缩如下:

public static int fib(int n) {int num_i = 0;int num_i_1 = 1;int num_i_2 = 1;for (int i = 2; i <= n; i++) {num_i = num_i_2 + num_i_1;num_i_2 = num_i_1;num_i_1 = num_i;}return num_i;
}
3)两种方法对比
  • 一般来说,由于备忘录的动态规划形式使用了递归,递归的时候会产生额外的开销,所以不推荐。
  • 相比之下,使用自底向上的动态规划方法要好些,也更容易理解。

1.3 动态规划的 3 大步骤

动态规划,无非就是利用 历史记录,来避免我们的重复计算。这些历史记录的存储,一般使用 一维数组二维数组 来保存。

第一步:定义数组元素的含义

  • 上面说了,我们用一个数组来保存历史数据,假设用一维数组 dp[] 来保存。这个时候有一个非常重要的点:如何规定数组元素的含义?dp[i] 代表什么意思?

第二步:找出数组元素之间的关系

  • 动态规划类似于我们高中学习的 数学归纳法。当我们要计算 d[i] 时,可以利用 dp[i-1]、dp[i-2] … dp[1] 来推导证明。

第三步:找出初始值

  • 学过 数学归纳法 的都知道,虽然知道了数组元素之间的关系式后,可以通过 dp[i-1] 和 dp[i-2] 来计算 dp[i],但是我们首先至少要知道 dp[0]dp[1] 才能推导后面的值。dp[0] 和 dp[1] 就是所谓的初始值。

二、小试牛刀:钢条切割

2.1 题目描述

在这里插入图片描述

2.2 题目解析

1)第一步:定义数组元素的含义

由题目可知:

  • p[] 是价格数组,长度为 i 英寸的钢条价格为 p[i]
  • r[] 是最大收益数组,长度为 i 英寸的钢条可以获得的最大收益为 r[i]
  • 钢条的价格不确定,可能切割的收益更高,也可能不切割的收益更高。

通过解析可知,数组元素含义: 长度为 i 英寸的钢条可以获得的最大收益为 r[i]

注意: 这里的 收益是指价格的总和,比如:2 英寸的钢条切割后收益为:1+1=2,相比之下不切割的 5 收益更高。

2)第二步:找出数组元素之间的关系

假如我们要对长度为 4 英寸的钢条进行切割,所有切割方案如下:

在这里插入图片描述

由图可见,我们将 r[4] 的计算转换成了 r[1]~ r[3] 的计算。
r 4 = m a x ( r 1 + r 3 , r 1 + r 1 + r 2 , r 2 + r 2 , p 4 ) ; r_{4}=max(r_{1}+r_{3},r_{1}+r_{1}+r_{2},r_{2}+r_{2},p_{4}); r4=max(r1+r3,r1+r1+r2,r2+r2,p4);
以此类推,可以继续转换 r[3]

由图可见,我们继续将 r[3] 的计算转换成了 r[1]~r[2] 的计算。
r 3 = m a x ( r 1 + r 2 , r 1 + r 1 + r 1 , p 3 ) r_{3}=max(r_{1}+r_{2},r_{1}+r_{1}+r_{1},p_{3}) r3=max(r1+r2,r1+r1+r1,p3)
以此类推,可以继续转换 r[2]

由于 1 英寸的钢条无法切割,所以 r[1]=p[1]
r 2 = m a x ( r 1 + r 1 , p 2 ) r_{2}=max(r_{1}+r_{1},p_{2}) r2=max(r1+r1,p2)
由于 r[2] 中包含了 r[1] + r[1],那么 r[3] 中的:
m a x ( r 1 + r 2 , r 1 + r 1 + r 1 ) = m a x ( r 1 + r 2 ) max(r_{1}+r_{2},r_{1}+r_{1}+r_{1})=max(r_{1}+r_{2}) max(r1+r2,r1+r1+r1)=max(r1+r2)
由于 r[3] 中包含了 r[1] + r[2],那么 r[4] 中的:
m a x ( r 1 + r 3 , r 1 + r 1 + r 2 ) = m a x ( r 1 + r 3 ) max(r_{1}+r_{3},r_{1}+r_{1}+r_{2})=max(r_{1}+r_{3}) max(r1+r3,r1+r1+r2)=max(r1+r3)
所以整理 r[1]r[2]r[3]r[4] 为:
r 1 = p 1 r_{1}=p_{1} r1=p1

r 2 = m a x ( r 1 + r 1 , p 2 ) r_{2}=max(r_{1}+r_{1},p_{2}) r2=max(r1+r1,p2)

r 3 = m a x ( r 1 + r 2 , p 3 ) r_{3}=max(r_{1}+r_{2},p_{3}) r3=max(r1+r2,p3)

r 4 = m a x ( r 1 + r 3 , r 2 + r 2 , p 4 ) r_{4}=max(r_{1}+r_{3},r_{2}+r_{2},p_{4}) r4=max(r1+r3,r2+r2,p4)

根据公式进行递推, r[n] 为:
r n = m a x ( r 1 + r n − 1 , r 2 + r n − 2 , . . . , r n / 2 + r n − n / 2 , p n ) r_{n}=max(r_{1}+r_{n-1},r_{2}+r_{n-2},...,r_{n/2}+r_{n-n/2},p_{n}) rn=max(r1+rn1,r2+rn2,...,rn/2+rnn/2,pn)

3)第三步:找出初始值

其实初始值我们在第二步已经找出来了:

  • r[1]=p[1]=1
  • r[2]=max(r[1]+r[1],p[2])=5

2.3 最优子结构

通过该题我们注意到,为了求规模为n的原问题,我们 先求解形式完全一样,但规模更小的子问题。当完成首次 切割后,我们 将两段钢条看成两个独立的钢条切割问题实例。我们 通过组合两个相关子问题的最优解,并在所有可能的两段切割方案中选取组合收益最大者,构成原问题的最优解

我们称 钢条切割问题 满足 最优子结构 性质:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。

2.4 代码实现

1)递归版本

递归很好理解,思路和回溯法是一样的,遍历所有解空间。但这里和上面斐波那契数列的不同之处在于:这里在每一层上都进行了一次最优解的选择,q=Math.max(q, p[i]+cut(n-i)); 这段代码就是选择最优解。

final static int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};public static int cut(int n) {if (n == 0) {return 0;}int max = Integer.MIN_VALUE;for (int i = 1; i <= n; i++) {max = Math.max(max, p[i - 1] + cut(n - i));}return max;
}
2)备忘录版本

备忘录方法无非是在递归的时候记录下已经调用过的子函数的值。钢条切割问题的经典之处在于自底向上的动态规划问题的处理,理解了这个也就理解了动态规划的精髓。

public static int cutByHis(int n) {int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};int[] r = new int[n + 1];for (int i = 0; i <= n; i++) {r[i] = -1;}return cut(p, n, r);
}public static int cut(int[] p, int n, int[] r) {int q = -1;if (r[n] >= 0)return r[n];if (n == 0)q = 0;else {for (int i = 1; i <= n; i++)q = Math.max(q, cut(p, n - i, r) + p[i - 1]);}r[n] = q;return q;
}
3)自底向上的动态规划

自底向上的动态规划问题中最重要的是要理解在子循环遍历中的 i 变量,相当于上面两个方法中的 n 变量,i-j 主要用于获取历史计算过的问题值。

final static int[] p = {1, 5, 8, 9, 10, 17, 17, 20, 24, 30};public static int cutByDP(int n) {int[] r = new int[n + 1];for (int i = 1; i <= n; i++) {int q = -1;for (int j = 1; j <= i; j++)q = Math.max(q, p[j - 1] + r[i - j]);r[i] = q;}return r[n];
}

整理完毕,完结撒花~ 🌻





参考地址:

1.算法-动态规划 Dynamic Programming–从菜鸟到老鸟,https://blog.csdn.net/u013309870/article/details/75193592

2.告别动态规划,连刷40道动规算法题,我总结了动规的套路,https://blog.csdn.net/hollis_chuang/article/details/103045322

相关文章:

数据结构与算法(二)动态规划(Java)

目录 一、简介1.1 什么是动态规划&#xff1f;1.2 动态规划的两种形式1&#xff09;自顶向下的备忘录法&#xff08;记忆化搜索法&#xff09;2&#xff09;自底向上的动态规划3&#xff09;两种方法对比 1.3 动态规划的 3 大步骤 二、小试牛刀&#xff1a;钢条切割2.1 题目描述…...

颜值实力“C位出道”:起亚EV6综合实力究竟怎么样?

作为起亚电动化转型的标杆之作&#xff0c;起亚EV6已在全球赢得广泛赞誉&#xff0c;连续斩获“2022欧洲年度汽车”及“2023北美年度汽车”等多项国际大奖&#xff0c;其GT版本更是荣获“2023年度世界性能车”&#xff0c;这些荣誉不仅标志着其设计和技术的国际认可&#xff0c…...

继承和多态_Java零基础手把手保姆级教程(超详细)

文章目录 Java零基础手把手保姆级教程_继承和多态&#xff08;超详细&#xff09;1. 继承1.1 继承的实现&#xff08;掌握&#xff09;1.2 继承的好处和弊端&#xff08;理解&#xff09; 2. 继承中的成员访问特点2.1 继承中变量的访问特点&#xff08;掌握&#xff09;2.2 sup…...

AI:85-基于深度学习的自然场景生成与渲染

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新中,…...

Windows电脑训练 RT-DETR 改进算法 (Ultralytics) 教程,改进RTDETR算法(包括使用训练、验证、推理教程)

手把手从零开始训练 RT-DETR 改进项目 (Ultralytics版本) 教程,改进RTDETR算法 本文以Windows服务器为例:从零开始使用Windows训练 RT-DETR 算法项目 《芒果剑指 RT-DETR 目标检测算法 改进》 适用于芒果专栏改进RT-DETR算法 文章目录 百度 RT-DETR 算法介绍改进网络代码汇…...

flask框架报错解决方法

1、报错 jinja2.exceptions.TemplateNotFound 解决方法&#xff1a;报错jinja2.exceptions.TemplateNotFound&#xff0c;template没找到&#xff0c;由于我之前直接将.html文件和.py文件直接放在同一目录下&#xff0c;经了解&#xff0c;需要新增一个 templates目录&#xff…...

Ubuntu18.04 安装docker教程

Ubuntu18.04 安装docker教程 1、前言 Docker Engine-Community 支持以下的 Ubuntu 版本&#xff1a; Xenial 16.04 (LTS)Bionic 18.04 (LTS)Cosmic 18.10Disco 19.04 Docker Engine-Community 支持以下CPU架构&#xff1a; x86_64&#xff08;或 amd64&#xff09;armhfarm…...

深入理解Git

目录 一、Git 的基本构造 1.1 关键对象类型 1.2 存储机制 二、Git 的内部工作 2.1 哈希和数据完整性 2.2 引用和可达性 2.3 分支和合并 2.4 垃圾回收 三、Git 高级特性 3.1 垃圾回收 3.2 钩子&#xff08;Hooks&#xff09; 3.3 子模块 四、常用命令 五、最佳实践…...

Leetcode_203.移除链表元素—C语言

目录 ❣️1.题目❣️ ❣️2.解答❣️ &#x1f49e;方法一&#xff1a;暴力法 &#x1f49e;方法二&#xff1a; 尾插法 &#x1f49e;方法三&#xff1a;哨兵位法 ❣️1.题目❣️ 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.va…...

虹科方案 | 汽车电子电气架构设计仿真解决方案

来源&#xff1a;虹科汽车电子 虹科方案 | 汽车电子电气架构设计仿真解决方案 导读 本文将介绍面向服务&#xff08;SOA&#xff09;的汽车TSN网络架构&#xff0c;并探讨RTaW-Pegase仿真与设计软件在TSN网络设计中的应用。通过RTaW将设计问题分解&#xff0c;我们可以更好地理…...

Java6种单例模式写法

单例模式 某个类任何情况下只有一个实例&#xff0c;并提供一个全局访问点来获取该实例。Java6种单例模式&#xff1a;2种懒汉式&#xff0c;2种饿汉式 &#xff0c;静态内部类 &#xff0c;枚举类懒汉式 synchronized延迟加载 public class Singleton {private static Sing…...

Direct3D拾取

假设在屏幕上单击&#xff0c;击中的位置为点s(x,y)。由图可以看出&#xff0c;用户选中了茶壶。但是仅给出点s&#xff0c;应用程序还无法立即判断出茶壶是否被选中。所以针对这类问题&#xff0c;我们需要采用一项称为“拾 取(Picking)”的技术。 茶壶和屏幕点s之间的一种联…...

大洋钻探系列之二IODP 342航次是干什么的?(上)

本文简单介绍一下大洋钻探IODP 342航次&#xff0c;从中&#xff0c;我们一窥大洋钻探航次的风采。 IODP342的航次报告在网络上可以下载&#xff0c;英文名字叫《Integrated Ocean Drilling ProgramExpedition 342 Preliminary Report》&#xff0c;航次研究的主要内容是纽芬兰…...

离散时间系统模型

离散时间系统模型 离散时间系统模型是表示数字滤波器的方案。MATLAB 科学计算环境支持若干种离散时间系统模型&#xff0c;这些模型将在以下章节中介绍&#xff1a; ​传递函数零极点增益状态空间部分分式展开式&#xff08;残差形式&#xff09;二阶节 (SOS)格型结构体卷积矩…...

Nginx学习(在 Docker 中使用 Nginx)

1. 安装Nginx 使用 docker pull nginx 下载最新的 Nginx Docker 镜像。 下载完毕后&#xff0c;使用 docker run -d -p 80:80 --name nginx nginx&#xff0c;即可启动 Nginx 容器。其中&#xff0c;-p 80:80 表示将容器的 80 端口映射到 主机的 80 端口&#xff1b;--name ng…...

【Java】集合(一)单列集合List

1.集合 可以动态保存任意多个对象&#xff0c;并提供了一系列的操作对象的方法&#xff1a;add、remove、set、get等。 2.集合框架体系 分为两大类&#xff1a; 单列集合和双列集合 3.List接口基本介绍 List接口是Collection接口的子接口 List集合类中元素有序&#xff0…...

实战 | 基于卷积神经网络的蘑菇识别微信小程序

一个不知名大学生&#xff0c;江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2023.11.13 Last edited: 2023.11.13 导读&#xff1a;其实没啥难的&#xff0c;主要是随手搞了就发出来把&#xff0c;太久没有水过帖子了&…...

如何选择共享wifi项目服务商,需要注意哪些?

在移动互联网时代&#xff0c;无线网络已经成为人们生活中不可或缺的一部分。随着5G时代的到来&#xff0c;共享WiFi项目成为了市场上备受关注的焦点。在众多共享WiFi公司中&#xff0c;如何选择共享wifi项目服务商合作&#xff0c;今天我们就来盘点下哪些公司可靠&#xff01;…...

ubuntu20.04 MYNTEYE S 相机运行与标定记录

ubuntu20.04 MYNTEYE S 相机运行与标定记录 环境 ubuntu20.04 opencv3.3.1 硬件 mynteye S1030 OpenCV 3.4.3 安装 Jetson Nano小觅相机(MYNT EYE S)开发调试指南 mkdir -p ~/tools/opencv cd ~/tools/opencvgit clone https://github.com/opencv/opencv.git cd opencv/…...

有效降低数据库存储成本方案与实践 | 京东云技术团队

背景 随着平台的不断壮大&#xff0c;业务的不断发展&#xff0c;后端系统的数据量、存储所使用的硬件成本也逐年递增。从发展的眼光看&#xff0c;业务与系统要想健康的发展&#xff0c;成本增加的问题必须重视起来。目前业界普遍认同开源节流大方向&#xff0c;很多企业部门…...

模型参数、模型存储精度、参数与显存

模型参数量衡量单位 M&#xff1a;百万&#xff08;Million&#xff09; B&#xff1a;十亿&#xff08;Billion&#xff09; 1 B 1000 M 1B 1000M 1B1000M 参数存储精度 模型参数是固定的&#xff0c;但是一个参数所表示多少字节不一定&#xff0c;需要看这个参数以什么…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

论文阅读笔记——Muffin: Testing Deep Learning Libraries via Neural Architecture Fuzzing

Muffin 论文 现有方法 CRADLE 和 LEMON&#xff0c;依赖模型推理阶段输出进行差分测试&#xff0c;但在训练阶段是不可行的&#xff0c;因为训练阶段直到最后才有固定输出&#xff0c;中间过程是不断变化的。API 库覆盖低&#xff0c;因为各个 API 都是在各种具体场景下使用。…...

掌握 HTTP 请求:理解 cURL GET 语法

cURL 是一个强大的命令行工具&#xff0c;用于发送 HTTP 请求和与 Web 服务器交互。在 Web 开发和测试中&#xff0c;cURL 经常用于发送 GET 请求来获取服务器资源。本文将详细介绍 cURL GET 请求的语法和使用方法。 一、cURL 基本概念 cURL 是 "Client URL" 的缩写…...

Vue 模板语句的数据来源

&#x1f9e9; Vue 模板语句的数据来源&#xff1a;全方位解析 Vue 模板&#xff08;<template> 部分&#xff09;中的表达式、指令绑定&#xff08;如 v-bind, v-on&#xff09;和插值&#xff08;{{ }}&#xff09;都在一个特定的作用域内求值。这个作用域由当前 组件…...

水泥厂自动化升级利器:Devicenet转Modbus rtu协议转换网关

在水泥厂的生产流程中&#xff0c;工业自动化网关起着至关重要的作用&#xff0c;尤其是JH-DVN-RTU疆鸿智能Devicenet转Modbus rtu协议转换网关&#xff0c;为水泥厂实现高效生产与精准控制提供了有力支持。 水泥厂设备众多&#xff0c;其中不少设备采用Devicenet协议。Devicen…...