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

news2024/12/26 22:52:34

目录

    • 一、简介
      • 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;
    // 声明数组,用于记录历史,初始化为-1
    int[] 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

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

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

相关文章

分发饼干(贪心算法+图解)

455. 分发饼干 - 力扣&#xff08;LeetCode&#xff09; 题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最…

wireshark打开tcpdump抓的包 vwr: Invalid data length runs past the end of the record

tcpdump -i any -n -s0 > t.pcap 使用此命令在Debian系统上抓包&#xff0c;下载到PC&#xff0c;用wireshark打开时报错&#xff1a; 后来发现写入文件时使用 -w 是没问题的&#xff0c;原因还不清楚。 tcpdump -i any -n -s0 -w t.pcap

JavaScript从入门到精通系列第三十一篇:详解JavaScript中的字符串和正则表达式相关的方法

文章目录 知识回顾 1&#xff1a;概念回顾 2&#xff1a;正则表达式字面量 一&#xff1a;字符串中正则表达式方法 1&#xff1a;split 2&#xff1a;search 3&#xff1a;match 4&#xff1a;replace 大神链接&#xff1a;作者有幸结识技术大神孙哥为好友&#xff0c;…

美国通胀预期高企,现货黄金价格继续承压下滑

上周五现货黄金持续振荡下滑&#xff0c;金价失守1940美元关口&#xff0c;最低至1933.17美元/盎司&#xff0c;最终收跌1.09%&#xff0c;报1936.51美元/盎司&#xff0c;创10月17日以来新低&#xff1b;今日&#xff08;周一&#xff09;截止汉声集团分析师发稿前&#xff0c…

竞赛选题 深度学习的动物识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

APS、SAP解析BOM批量核对(我的APS项目三)

APS提供了解析BOM接口 SAP从CU50中解析了BOM 博主开发了一个程序&#xff0c;把两边的BOM数据拉到一起来比对&#xff0c;从最初的一个车型&#xff0c;增加到5个车型&#xff0c;最后成型是30个车型&#xff0c;几乎覆盖了F1、F2的全部车型。 并且程序还实现了消息提醒功能&…

PLM/ERP/APS/MES/SRM/CRM/WMS/QMS

参考一 ERP 1 什么是ERP ERP的英文全称是“Enterprise Resource Planning”&#xff0c;从字面上看&#xff0c;它的意思就是“企业资源计划”。ERP最开始是由美国著名的计算机技术咨询和评估集团Garter Group提出的一整套企业管理系统体系标准。 2 ERP的发展历程&#xff08…

【数据仓库】数仓分层方法

文章目录 一. 数仓分层的意义1. 清晰数据结构。2. 减少重复开发3. 方便数据血缘追踪4. 把复杂问题简单化5. 屏蔽原始数据的异常6. 数据仓库的可维护性 二. 如何进行数仓分层&#xff1f;1. ODS层2. DW层2.1. DW层分类2.2. DWD层2.3. DWS 3. ADS层 4、层次调用规范 一. 数仓分层…

自然语言处理实战项目21-两段文本的查重功能,返回最相似的文本字符串,可应用于文本查重与论文查重

大家好,我是微学AI,今天给大家介绍一下自然语言处理实战项目21-两段文本的查重功能,返回最相似的文本字符串,可应用于论文查重。本文想实现一种文本查重功能,通过输入两段文本,从中找出这两段文本中最相似的句子。这项技术有助于检测抄袭、抄袭的论文和文章,提高知识创新…

js设置图片放大缩小拖动

效果: 思路: 在外层box进行相对定位relative,img设置绝对定位absolute;通过监听滚轮事件(wheel),设置样式缩放中心点(transformOrigin)和缩放转换(transform);获取到图片大小和位置,设置对应图片宽度高度和top、left偏移;鼠标按下事件(mousedown)和鼠标移动事…

【教3妹学编程-算法题】给小朋友们分糖果 II

3妹&#xff1a;1 8得8&#xff0c;2 816&#xff0c; 3 8妇女节… 2哥 : 3妹&#xff0c;在干嘛呢 3妹&#xff1a;双11不是过了嘛&#xff0c; 我看看我这个双十一买了多少钱&#xff0c; 省了多少钱。 2哥 : 我可是一分钱没买。 3妹&#xff1a;我买了不少东西&#xff0c; …

WS2812B彩灯 STM32HAL库开发:PWM+DMA(stm32f103c8t6)

目录 一、摘要 二、WS2812B介绍 三、CUBEMX配置 四、程序介绍&#xff08;KEIL编译器&#xff09; 五、数据手册 一、摘要 1、本文使用示例单片机型号为stm32f103c8t6&#xff0c;RGB型号为WS2812B&#xff1b; 2、主要实现功能是实现用PWMDMA使RGB_LED亮起不同颜色的灯光…

暖手宝上架亚马逊美国站UL499报告测试标准要求

暖手宝是运用物理及化学原理研制的自动取暖保健用品。该产品以其自动生热&#xff0c;有趣&#xff0c;实用等新颖独特的优势&#xff0c;深受欢迎——暖手宝具有自动取暖&#xff0c;理疗保健等多种功能。只要插上电源等上10分钟左右就能发热&#xff0c;最后一种是通过锂电池…

thinkphp6 只有默认页能访问 其他404 其他模块404

1.只有默认页能访问 其他页404 同时隐藏index.php 在 public/.htaccess 中添加如下配置&#xff0c;后重启服务 <IfModule mod_rewrite.c>Options FollowSymlinks -MultiviewsRewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-dRewriteCond %{REQUEST_FILENAME} !-f…

活跃类指标

活跃类指标反映了用户的真实使用情况。本节我们深入探讨活跃类指标的核心逻辑。 1&#xff0e; UV UV ( Unique Visitor &#xff0c;独立访客&#xff09;&#xff0c;是所有活跃类指标的基础。 既然叫独立访客&#xff0c;何谓之独立&#xff1f; APP 产品界定独立访客相对…

算法的复杂性

通常情况下&#xff0c;一个问题可能对应有多种解决方案&#xff0c;每种解决方案都是一种算法。因此&#xff0c;我们可能经常需要做一件事&#xff1a;从众多算法中挑选出一个最好的算法。所谓“最好”的算法&#xff0c;即最适合当前场景使用的算法。 不同的应用场景&#x…

IT 基础架构管理需要了解的信息

各行各业的现代组织不断面临创新和扩展的压力。就在十多年前&#xff0c;一个组织可以争取时间&#xff0c;在投资新技术时保持保守&#xff0c;同时仍然保持竞争优势&#xff0c;快进到今天&#xff0c;随着商业实践的变化和新技术的不断涌现&#xff0c;商业和技术领域变得更…

瑞利长度(Rayleigh length)

瑞利长度 Rayleigh length 在光学&#xff0c;特别是激光学中&#xff0c;我们设鞍腰部&#xff08;如图中所示的最低处&#xff09;为A&#xff0c;其横截面面积为a&#xff0c;沿光的传播方向&#xff0c;当横截面面积因为散射达到2a时&#xff0c;我们设此处为B&#xff0c;…

Mysql-表的结构操作

1.创建表 CREATE TABLE table_name ( field1 datatype, field2 datatype, field3 datatype ) character set 字符集 collate 校验规则 engine 存储引擎 ; 说明&#xff1a; field 表示列名 datatype 表示列的类型 character set 字符集&#xff0c;如果没有指定字…

从0到0.01入门React | 003.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…