| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 数据结构与算法 -> 算法 - 初识动态规划(Dynamic Programming) -> 正文阅读 |
|
[数据结构与算法]算法 - 初识动态规划(Dynamic Programming) |
目录 递归与动态规划之间的联系(Recursion and Dynamic Programming): 引言:动态规划是算法中的一个重点,我第一次碰见它是在大二下学期的学位课 ——《算法设计与分析》,无奈当时只是为了应付学校的期末考试学的非常浅,最近在B站上看到了关于动态规划算法的视频,并再次对动态规划进行学习,所以写这篇文章来对动态规划算法的重新学习进行一个记录。 分析:递归(Recursion):为什么在这里要提到递归?其实递归算法和动态规划算法之间是有联系的,我们在后面分析递归实现斐波那契数列时会明确这一点。 在我之前的这篇文章C语言-8月1日-递归与动态内存管理中曾经写过关于递归的知识,在这里对递归的定义再提一遍:
递归个人认为也是一个极具数学思维的编程技巧,因为在递归的过程中包含了把一个复杂问题分解为多个规模较小的问题,还有分解完成之后将等规模的问题合并为原问题规模的过程。在之前的文章里我曾经使用递归技巧实现了斐波那契数列、杨辉三角、约瑟夫环等数学问题,在实现杨辉三角的三种主流方法(一维数组、二维数组、递归)里递归也是占用内存最小且效率最高的那一个。好了,废话不多说,我们来分析斐波那契数列。 递归斐波那契数列(Fibonnaci Sequence):斐波那契数列在之前的文章中已经使用递归技巧实现过并分析过函数调用的过程,这里给出代码和树状图: 斐波那契数列的递归函数:
斐波那契数列递归形式的分析树状图:?在这张图中我们可以看到我们要求的是斐波那契数列中的第六个数字,我们将要求的第六个数字的个问题分解为要求第五个数字和第四个数字,然后又分别将求第五个数字和求第四个数字分解为求第四个数字和第三个数字与求第三个数字和第二个数字,第二个数字和第一个数字其实也就是递归出口1,所以此时第三个数字为2,第四个数字就为3,第五个数字就为5,第六个数字就为8,数字8就是最终的结果。 但是在这张树状图里,我们却进行了一个极度消耗内存空间的行为——函数调用。 如图,例如我们在已经通过第三层的左子树Fibo(4)下面的子树求出Fibo(4)的前提下,还要通过第二层右子树下面的调用进行再一次的求值,也就是说要求的值每增长一个树的规模就会放大一倍,所以基于这种方式下的递归斐波那契数列这个程序的时间复杂度就是恐怖的O(2^n). 递归与动态规划之间的联系(Recursion and Dynamic Programming):上面我们已经分析了常规状态下递归形式的斐波那契数列的求解方式,但是由于这种方式的时间复杂度过大,我们由此引入一个新的知识点:重叠子问题(Overlap sub-problem) 重叠子问题(Overlap sub-problem)在斐波那契数列求解的树状图中,我们要进行多次并重复的函数调用,且调用的次数是呈指数级增长的,但是如果我们把每一次算出来的值保存起来在后面直接进行调用,例如我们需要继续按斐波那契数列的第六个树,在第四个数和第五个树已经算出来的前提下,我们直接拿出来用,复杂程度就会简单不少,且能加速整个运算过程的速度这里画一张图我们来看一下这个过程: 这时程序的时间复杂度就变为了:O(n) 其实重叠子问题(Overlap sub-problem)就是后面动态规划要使用到的思想。 动态规划(Dynamic Programming):在王晓东的《算法设计与分析》中,对动态规划是这样介绍的:动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。 动态规划算法适用于解最优化问题,通常可以按以下步骤设计动态规划算法: (1)找出最优解的性质,并刻画其结构特征。 (2)递归地定义最优值 (3)以自底向上的方式计算出最优值 (4)根据计算最优解时得到的信息,构造最优解 我们通过下面两个例子来理解这四个步骤: 动态规划最优解问题:问题描述:此时我们要在外面工作赚钱,下图是在不同的时间段工作可以拿到的报酬,例如我们选择工作1可以拿到的报酬是5元,但是在选择了工作1之后因为时间冲突就不能选择工作2或是工作3以此往后类推,我们现在要算出怎么安排今天的工作,能将我们所能获取到的利益进行最大化,求出最优解: 问题分析:?在这个题目中,牵扯到了一个选择问题,就是对于这个时间段的工作的选或不选问题 此时设置三个参量分别是Opt、Prev、V,前两个的意思分别对应着单词optimize(使最优化)和previous(先前的),Opt选择最优值,Prev代表这个选择之前的最优选择,V代表这个选择对应的价值。 这里举一个例子来使我们快速切入: 例如我在这里第一次瞄准工作8,我们就有了两种选择,就是选择工作8和不选工作8,可以这样表示: 如果我们选择工作8的话,我们就要在前5份工作中找出最优值并加上工作8所能产生的收益,如果不选,我们就直接在前7份工作里面找出最优值。先来画一张图来将Prev和Opt的关系搞清楚: ?我们这样理解这张图:
综上所述,这个问题的最优解就是13。这也就是自底向上求出最优值。 不相邻数字相加和最大问题:问题描述:在若干个存放在数组的数字中,我们需要找出彼此不相邻的数字来使她们相加起来的和最大 ,而若干个数字就有若干种选择,例如我在这里给出7个数字均存放在0-6号下标的数组中,分别是: 如果我在这时选择了元素4,此时他的不相邻元素就是,0号下标的1元素、4号下标的7元素、6号下标的3元素。 我们现在要做的就是要求这一个序列中所存在的不相邻元素和的最大值,而这取决于我们选择哪一个。 问题分析:首先我们设置两个形参,分别是OPT,i,i表示第几个元素,OPT用来表示第一个元素到第i个元素的最大和值。
树状图形式分析:图中绿色方框框住的表达式即是重叠子问题,可以通过记忆功能相互代入以有效的降低递归的时间复杂度。 表格形式分析:注:蓝色方框对应的是随着遍历元素个数的增加,第一个到遍历到的第i个元素的最大和值。相同的值代表的就是重叠子问题,可以互相代入,以降低程序时间复杂度。 规律总结:此时分析过程我们已经基本清楚,现在把分析出来的规律写成方程: ?
?代码实现(递归方法):
运行结果:代码实现:动态规划(重叠子问题记忆功能)?
?运行结果:两种方法的对比:??第一种方法为递归方法,没有很好的利用重叠子问题,空间的代价非常高,时间复杂度为:O(2(^n)); 第二种方法为动态规划算法,利用了重叠子问题并相互代入,有效地将时间复杂度降为了O(n). 参考资料: |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/25 20:42:50- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |