数据结构---动态规划

news2025/2/1 18:03:52

动态规划

  • 爬楼梯问题
    • 解法1
      • 第一步
      • 第二步
      • 第三步
      • JAVA实现
    • 解法2
      • 问题建模
        • 最优子结构
        • 边界
        • 状态转移公式
      • 求解问题
        • 递归JAVA实现
        • 备忘录算法JAVA实现
        • 解法三JAVA实现(斐波那契数列)
  • 国王和金矿
    • 一个错误的解法
    • 排列组合解法
      • JAVA实现
    • 动态规划

爬楼梯问题

有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上1级或者2级台阶。要求用程序来求出一共有多少种走法。

解法1

利用排列组合公式求解此题,穷举出所有的组合情况。。。。

第一步

  1. 台阶个数10阶
  2. 一次上1阶或2阶—>(求利用1和2组成10的所有排列数目)
  3. 一旦1的数目确定,2的数目也确定了
  4. 1上限100
  5. 2上限50

第二步

设置循环

        int i ;//一次爬2阶楼梯的次数
        int j ;//一次爬1阶楼梯的次数
        for (i=0;i<51;i++){
            j = 100-2*i;
        }

第三步

计算每个循环中,组合数量是多少(就是说1和2的数目确定了,求不同顺序排列的数目)
在这里插入图片描述
在这里插入图片描述
其中n是所有的1和2的数目,只要确定1或者2的排列,总的顺序就确定了。
在这里插入图片描述

JAVA实现

//求阶乘方法
    public static long factorialUsingForLoop(int n) {
        long fact = 1;
        for (int i = 2; i <= n; i++) {
            fact = fact * i;
        }
        return fact;
    }
    /**
     * 求n阶楼梯的所有爬法
     * @param n  多少阶楼梯
     * @return
     */
    public static long sumclimbingStairs(int n){
        int i ;//一次爬2阶楼梯的次数
        int j ;//一次爬1阶楼梯的次数
        long a ;
        long b;
        long c;
        long sum=0;//累加每次排列的数目,计算总的数目
        for (i=0;i<=(n/2);i++){
            j = n-(2*i);
            //这里注意总数目是i+j不是n........
            a = factorialUsingForLoop(i+j);
            b = factorialUsingForLoop(j);//这里是Cn1(计算的1的排列)
            c = factorialUsingForLoop(i);
            sum =sum+ a/(b*c);
        }
        return sum;
    }

测试方法

    public static void main(String[] args) {
        System.out.println(sumclimbingStairs(1));
        System.out.println(sumclimbingStairs(2));
        System.out.println(sumclimbingStairs(3));
        System.out.println(sumclimbingStairs(4));
        System.out.println(sumclimbingStairs(5));
        System.out.println(sumclimbingStairs(6));
        System.out.println(sumclimbingStairs(7));
        System.out.println(sumclimbingStairs(8));
        System.out.println(sumclimbingStairs(9));
        System.out.println(sumclimbingStairs(10));
        System.out.println(sumclimbingStairs(11));
        System.out.println(sumclimbingStairs(12));
        System.out.println(sumclimbingStairs(13));
        System.out.println(sumclimbingStairs(14));
        System.out.println(sumclimbingStairs(15));
        //System.out.println(factorialUsingForLoop(4));
    }

在这里插入图片描述

参考

解法2

动态规划(Dynamic Programming)是一种分阶段求解决策问题的数学思想

大事化小,小事化了。。
把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。

例子:爬10层楼梯 (假设爬前九层楼梯有X种方法,爬前八层楼梯有Y种方法)

  1. 完成前九层爬的楼梯数目+再爬一层
  2. 完成前八层爬的楼梯数目+再爬两层
  3. 爬10层楼梯一共的方法数目:X+Y种方法

在这里插入图片描述

从0到10级台阶的走法数量=0到9级的走法数量+0到8级的走法数量。
把10级台阶的走法数量简写为F(10),此时F(10)=F(9)+F(8)

关键在于计算F(9)和F(8)

对于F(9)和F(8),我们有
F(9)=F(8)+F(7), F(8)=F(7)+F(6)

动态规划的思想:把一个复杂的问题分阶段进行简化,逐步简化成简单的问题。

直到递推到1级台阶和2级台阶

F(1) = 1
F(2) = 2
F(n) = F(n-1)+F(n-2)

动态规划的核心:最优子结构、边界、状态转移公式

问题建模

最优子结构

F(10)=F(9)+F(8)
所以F(9)和F(8)是F(10)的最优子结构

边界

F(1)和F(2)我们可以直接得到结果,
所以F(1)和F(2)是问题的边界

状态转移公式

F(n) = F(n-1)+F(n-2)是状态转移方程

求解问题

递归JAVA实现

边界对应递归出口,状态转移方程可以用递归实现

/**
     * 求解爬楼梯
     * @param n  楼梯的阶数
     * @return
     */
    public static int getClimbingWays(int n){
        //递归出口
        if(n<1){
            return 0;
        }
        if(n==1){
            return 1;
        }
        if(n==2){
            return 2;
        }
        //递归调用
        return getClimbingWays(n-1)+getClimbingWays(n-2);
    }

测试方法

public static void main(String[] args) {
        System.out.println(getClimbingWays(1));
        System.out.println(getClimbingWays(2));
        System.out.println(getClimbingWays(3));
        System.out.println(getClimbingWays(4));
        System.out.println(getClimbingWays(5));
        System.out.println(getClimbingWays(6));
        System.out.println(getClimbingWays(7));
        System.out.println(getClimbingWays(8));
        System.out.println(getClimbingWays(9));
        System.out.println(getClimbingWays(10));
        System.out.println(getClimbingWays(11));
        System.out.println(getClimbingWays(12));
        System.out.println(getClimbingWays(13));
        System.out.println(getClimbingWays(14));
        System.out.println(getClimbingWays(15));
    }

在这里插入图片描述

就是把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。

计算出F(N),就要先得到F(N-1)和F(N-2)的值。要计算F(N-1),就要先得到F(N-2)和F(N-3)的值…以此类推,可以归纳成下面的数图:
在这里插入图片描述

时间复杂度O(2n)

存在的问题,很多重复的计算。。。
在这里插入图片描述
相同颜色是一样的传参,一样的计算。。

备忘录算法JAVA实现

针对以上问题,先创建一个哈希表,每次把不同参数的计算结果存入哈希。当遇到相同参数时,再从哈希表里取出,就不用重复计算了。

暂存计算结果

 public static int getClimbingWays(int n, HashMap<Integer,Integer> map){
        if(n<1){
            return 0;
        }
        if(n==1){
            return 1;
        }
        if(n==2){
            return 2;
        }
//        //递归调用  (改写这个)
//        return getClimbingWays(n-1)+getClimbingWays(n-2);

        /**
         * 集合map是一个备忘录。当每次需要计算F(N)的时候,
         * 会首先从map中寻找匹配元素。如果map中存在,就直接返回结果,
         * 如果map中不存在,就计算出结果,存入备忘录中。
         */
        if(map.containsKey(n)){
            return map.get(n);
        }else {
            int value=  getClimbingWays(n-1,map)+getClimbingWays(n-2,map);
            map.put(n,value);
            return value;

        }
    }

测试方法:

public static void main(String[] args) {
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        System.out.println(getClimbingWays(10,map));

    }

在这里插入图片描述

集合map是一个备忘录。当每次需要计算F(N)的时候,会首先从map中寻找匹配元素。如果map中存在,就直接返回结果,如果map中不存在,就计算出结果,存入备忘录中。

时间复杂度:O(N),算过一次后直接取值
空间复杂度:O(N),因为空间存了n个值

if(map.containsKey(n)){
            System.out.println("直接取值:f("+n+")");
            return map.get(n);
        }else {
            int value=  getClimbingWays(n-1,map)+getClimbingWays(n-2,map);
            map.put(n,value);
            return value;

        }

在这里插入图片描述

解法三JAVA实现(斐波那契数列)

优化空间复杂度:自底向下,用迭代的方式推导出结果。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一次迭代过程中,只要保留之前的两个状态,就可以推导出新的状态。而不需要像备忘录算法那样保留全部的子状态
优化空间复杂度
起其实这就是一个斐波那契数列

 /**
     * 爬楼梯
     * @param n 楼梯数目
     * @return
     */
    public static int getClimbingWays(int n){
        if(n<1){
            return 0;
        }
        if(n==1){
            return 1;
        }
        if(n==2){
            return 2;
        }
        int a = 1;
        int b = 2;
        int temp = 0;

        //自底向上---斐波那契数列
        for (int i=3;i<=n;i++){
            temp = a+b;
            a = b;
            b = temp;
        }
        return temp;
    }

测试方法:

    public static void main(String[] args) {
        for (int i =1;i<16;i++){
            System.out.println(getClimbingWays(i));
        }
    }

在这里插入图片描述

时间复杂度:O(n)
空间复杂度O(1)
迭代过程中只需保留两个临时变量a和b,分别代表了上一次和上上次迭代的结果。 为了便于理解,我引入了temp变量。temp代表了当前迭代的结果值


国王和金矿

很久很久以前,有一位国王拥有5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人人数也不同。例如有的金矿储量是500kg黄金,需要5个工人来挖掘;有的金矿储量是200kg黄金,需要3个工人来挖掘……如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半的金矿。要求用程序求出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿
在这里插入图片描述

这是一个典型的动态规划题目,和著名的“背包问题”类似。

动态规划,就是把复杂的问题简化成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到复杂问题的最优解。

一个错误的解法

使用贪心算法
按照金矿的性价比从高到低进行排序,优先选择性价比最高的金矿来挖掘,然后是性价比第2的……
在这里插入图片描述
总工人10,挖掘了第一名和第二名的金矿后,剩下的2人就没法挖掘其他金矿了。
总收益:350+500=850.

局部情况下是最优解,但是在整体上却未必是最优的。

如果我放弃性价比最高的350kg黄金/3人的金矿,选择500kg黄金/5人和400kg黄金/5人的金矿,加起来收益是900kg。

排列组合解法

每一座金矿都有挖与不挖两种选择,如果有N座金矿,排列组合起来就有2N种选择。对所有可能性做遍历,排除那些使用工人数超过10的选择,在剩下的选择里找出获得金币数最多的选择。

JAVA实现

动态规划

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

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

相关文章

rk3568 添加gc2053摄像头驱动

在设备树文件代码中添加 gc2053 设备节点 &i2c2 {status "okay";pinctrl-0 <&i2c2m1_xfer>;/* split mode: lane0/1 */gc2053: gc205337 {status "okay";compatible "galaxycore,gc2053";// 需要与驱动中的匹配字符串一致reg …

高级篇-rabbitmq的高级特性

1.消息可靠性 三种丢失的情形&#xff1a; 1.1 生产者确认机制 启动MQ 创建Queues&#xff1a; 两种Callback: 1.ReturnCallback:全局callback 2.ComfirmCallback: 发送信息时候设置 Testpublic void testSendMessage2SimpleQueue() throws InterruptedException {// 1.…

第八章练习题-3

目录 第十三题 题目 Student类 Teacher类 main类 老师的代码 Student类 Teacher类 main类 第七问&#xff1a;定义多态数组 main类 运行结果 问题 原因 解决办法 老师代码&#xff1a;main类 结果 第八问 运行结果 第十四题 第十五题 题目 方法的多态…

HFSS使用经验三

目录 一、如何量取HFSS某一点的空间位置 二、如何快速的切换视图 三、HFSS中绘制圆柱体 四、如何修改HFSS的仿真线程数量 五、HFSS中如何选取挖空的面 六、HFSS中如何实现参数扫描 一、如何量取HFSS某一点的空间位置 右击可以点击Measure按键 鼠标放到特殊点&#xff0c…

2023年申请发明专利的重要性和注意问题。

随着对知识产权意思的逐步提高&#xff0c;企业对知识产权越来越关心。知识产权包括专利权、商标权和著作权。专利包含发明专利专利、实用新型专利、外观设计专利。其中发明专利的申请难度最大&#xff0c;含金量最高。根据小编申请发明专利方面20年的经验&#xff0c;简单介绍…

【Windows|WSL|Ubuntu|VSCode】流程记录|坑点模糊回忆

无限踩坑&#xff0c;悲惨回忆&#xff0c;又似乎毫无意义&#xff1f; 1.安装WSL 官方文档&#xff1a;Install WSL | Microsoft Learn 简单来说&#xff0c;管理员身份运行PowerShell wsl --install 通常需要wsl2&#xff0c;得益于其优势&#xff0c;通过 PowerShell w…

用ERP系统做数据管理对企业有什么好处?

ERP系统数据管理解决方案是以业务为主导的工具&#xff0c;为所有的业务流程和操作创建一个单一的主记录&#xff0c;包括来自各种内部和外部应用和来源的人员和系统。 大多数企业采用不同的系统&#xff0c;包含客户、产品、销售、交易等信息数据存储在许多不同的地方&#…

葵花宝典之C语言冷知识(二)

目录 &#x1f693;&#xff08;一&#xff09;图形的打印 &#x1f699;判断类型 &#x1f68c;逻辑简单易找规律型。 &#x1f68c;存在坐标规律的图案打印 &#x1f693;&#xff08;二&#xff09;中值的表达形式 &#x1f699;(xy)/2 表达错误的原因 &#x1f699;有符号数…

Python编程课程好学吗?能学会吗?

Python编程课程好学吗&#xff1f;能学会吗&#xff1f;Python是一种计算机程序设计语言&#xff0c;一种面向对象的动态类型语言&#xff0c;最初被设计用于编写自动化脚本(shell)&#xff0c;随着版本的不断更新和语言新功能的添加越来越多被用于独立的大型项目的开发。 编程…

mvn和npm的那些事

1 mvn简介 mvn就好比java中的maven,用于管理包版本,mvn用于管理node版本,而npm来源于node中,比如用于拉取仓库中的依赖包,在构建使用项目时可以选择指定的版本,从而避免编译出错运行失败等问题 Node.js17或更高版本中出现Error: error:0308010C:digital envelope routine…

CSDN如何使用Markdown编辑器

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

Pytest自动化测试 - 完美结合Allure

简介 Allure Framework是一种灵活的、轻量级、多语言测试报告工具。 不仅可以以简洁的网络报告形式非常简洁地显示已测试的内容&#xff0c; 而且还允许参与开发过程的每个人从日常执行中提取最大程度的有用信息和测试。 从开发/测试的角度来看&#xff1a; Allure报告可以…

四大蓝牙天线设计方式

https://www.elecfans.com/d/686538.html 一直以来&#xff0c;无论是智能手机&#xff0c;还是笔记本电脑&#xff0c;亦或是平板电脑&#xff0c;蓝牙都是智能设备的标配。随着移动互联网的发展&#xff0c;现在涌现出大量的智能可穿戴设备&#xff0c;而支撑这些应用的发展不…

Spring AOP源码解析——专治你不会看源码的坏毛病!

虽然现在大厂内卷现象泛滥&#xff0c;而且996的传统依旧肆虐。但没有哪位程序员能架得住互联网大厂的高薪职位诱惑。特别是我还有一位在阿里工作7年多的老表&#xff0c;在其耳旁风之下&#xff0c;不断将大厂描绘的美丽风景刻画在我脑海中&#xff0c;也让我一直有着想进大厂…

架构设计(八):数据库的水平扩展和垂直扩展

架构设计&#xff08;八&#xff09;&#xff1a;数据库的水平扩展和垂直扩展 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;架构设计&#xff08;八&#xff09;&#xff1a;数据库的水平扩展和垂直扩展 CSDN&#xff1a;架构设计&#xff08;八&#xf…

Redis6入门到实战------ 三、常用五大数据类型(列表(List)、集合(Set)、哈希(Hash)、Zset(sorted set))

3 Redis列表(List) 3.1 简介 单键多值 Redis 列表是简单的字符串列表&#xff0c;按照插入顺序排序。你可以添加一个元素到列表的头部&#xff08;左边&#xff09;或者尾部&#xff08;右边&#xff09;。 它的底层实际是个双向链表&#xff0c;对两端的操作性能很高&#x…

组件技术--设计--MVC模式+DAO+MySQL+jsp+servlet 简单的购物车案例

MVC模式DAOMySQLjspservlet 简单的购物车案例题外话购物车案例需求核心系统组成Javaweb项目框架基本思想核心代码DaoBookDaolistenerSessionListenerservletAddServletInitServletRemoveServletvoBook.jspbuyForm.jspshowAllBook.jspshowCart.jsptargetpom.xmlWEB-INFweb.xmlli…

python绘图——坐标轴

1. 2D坐标轴 1.1 绘制简单的曲线 import matplotlib.pyplot as plt import numpy as np xnp.linspace(-1,1,50)#-1到1中画50个点 yx**2 plt.plot(x,y,colorgreen) plt.tick_params(axisx,colorsblue) plt.tick_params(axisy,colorsred) plt.show()作图&#xff1a; 1.2 坐标…

游戏开发 LinkedList

inkedList底层是双向链表&#xff0c;一个节点挂着一个节点LinkedList不需要设定长度&#xff0c;不需要扩容LinkedList 的优缺点 优点 ① 往里面插入一些元素的时候不需要像ArrayList数组那样需要挪动大量的元素了&#xff0c;直接在链表里加入一个节点就可以了 ② 如果要不断…

【数据结构】LinkedList与双向链表

目录 一、认识集合类LinkedList 1、认识 2、构造方法 二、实现双向非循环链表 1、准备 2、方法的实现 1.遍历 2.有效节点个数 3.是否包含key 4.清空链表 5.头插法 6.尾插法 7.指定位置插入 8.删除第一次出现的key 9.删除所有的key 一、认识集合类LinkedList 1、认…