当前位置: 首页 > 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;很多企业部门…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;百货中心供应链管理系统被用户普遍使用&#xff0c;为方…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

【C语言练习】080. 使用C语言实现简单的数据库操作

080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比

目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec&#xff1f; IPsec VPN 5.1 IPsec传输模式&#xff08;Transport Mode&#xff09; 5.2 IPsec隧道模式&#xff08;Tunne…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

怎么让Comfyui导出的图像不包含工作流信息,

为了数据安全&#xff0c;让Comfyui导出的图像不包含工作流信息&#xff0c;导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo&#xff08;推荐&#xff09;​​ 在 save_images 方法中&#xff0c;​​删除或注释掉所有与 metadata …...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

从面试角度回答Android中ContentProvider启动原理

Android中ContentProvider原理的面试角度解析&#xff0c;分为​​已启动​​和​​未启动​​两种场景&#xff1a; 一、ContentProvider已启动的情况 1. ​​核心流程​​ ​​触发条件​​&#xff1a;当其他组件&#xff08;如Activity、Service&#xff09;通过ContentR…...

Java多线程实现之Runnable接口深度解析

Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...