看动画轻松理解“递归”与“动态规划”

在学习「数据结构和算法」的过程中,因为人习惯了平铺直叙的思维方式,所以「递归」与「动态规划」这种带循环概念(绕来绕去)的往往是相对比较难以理解的两个抽象知识点。

程序员小吴打算使用动画的形式来帮助理解「递归」,然后通过「递归」的概念延伸至理解「动态规划」算法思想。

什么是递归

先下定义:递归算法是一种直接或者间接调用自身函数或者方法的算法。

通俗来说,递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。它有如下特点:

一个问题的解可以分解为几个子问题的解

这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

存在递归终止条件,即必须有一个明确的递归结束条件,称之为递归出口

递归动画

通过动画一个一个特点来进行分析。

1.一个问题的解可以分解为几个子问题的解

子问题就是相对与其前面的问题数据规模更小的问题。

在动图中①号问题(一块大区域)划分为②号问题,②号问题由两个子问题(两块中区域)组成。

2.这个问题与分解之后的子问题,除了数据规模不同,求解思路完全一样

「①号划分为②号」与「②号划分为③号」的逻辑是一致的,求解思路是一样的。

3.存在递归终止条件,即存在递归出口

把问题分解为子问题,把子问题再分解为子子问题,一层一层分解下去,不能存在无限循环,这就需要有终止条件。

①号划分为②号,②号划分为③号,③号划分为④号,划分到④号的时候每个区域只有一个不能划分的问题,这就表明存在递归终止条件。

从递归的经典示例开始一、数组求和

数组求和

11Sum(arr[0...n-1])=arr[0]+Sum(arr[1...n-1])

后面的Sum函数要解决的就是比前一个Sum更小的同一问题。

11Sum(arr[1...n-1])=arr[1]+Sum(arr[2...n-1])

以此类推,直到对一个空数组求和,空数组和为0,此时变成了最基本的问题。

11Sum(arr[n-1...n-1])=arr[n-1]+Sum([])

二、汉诺塔问题

汉诺塔(HanoiTower)问题也是一个经典的递归问题,该问题描述如下:

汉诺塔问题:古代有一个梵塔,塔内有三个座A、B、C,A座上有64个盘子,盘子大小不等,大的在下,小的在上。有一个和尚想把这个盘子从A座移到B座,但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘子始终保持大盘在下,小盘在上。

两个盘子

①如果只有1个盘子,则不需要利用B塔,直接将盘子从A移动到C。

②如果有2个盘子,可以先将盘子2上的盘子1移动到B;将盘子2移动到C;将盘子1移动到C。这说明了:可以借助B将2个盘子从A移动到C,当然,也可以借助C将2个盘子从A移动到B。

③如果有3个盘子,那么根据2个盘子的结论,可以借助C将盘子3上的两个盘子从A移动到B;将盘子3从A移动到C,A变成空座;借助A座,将B上的两个盘子移动到C。

④以此类推,上述的思路可以一直扩展到n个盘子的情况,将将较小的n-1个盘子看做一个整体,也就是我们要求的子问题,以借助B塔为例,可以借助空塔B将盘子A上面的n-1个盘子从A移动到B;将A最大的盘子移动到C,A变成空塔;借助空塔A,将B塔上的n-2个盘子移动到A,将C最大的盘子移动到C,B变成空塔……

三、爬台阶问题

问题描述:

一个人爬楼梯,每次只能爬1个或2个台阶,假设有n个台阶,那么这个人有多少种不同的爬楼梯方法?

先从简单的开始,以4个台阶为例,可以通过每次爬1个台阶爬完楼梯:

可以通过先爬2个台阶,剩下的每次爬1个台阶爬完楼梯

先爬2个台阶

在这里,可以思考一下:可以根据第一步的走法把所有走法分为两类:

①第一类是第一步走了1个台阶②第二类是第一步走了2个台阶

所以n个台阶的走法就等于先走1阶后,n-1个台阶的走法,然后加上先走2阶后,n-2个台阶的走法。

用公式表示就是:

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

有了递推公式,递归代码基本上就完成了一半。那么接下来考虑递归终止条件。

当有一个台阶时,我们不需要再继续递归,就只有一种走法。

所以f(1)=1。

通过用n=2,n=3这样比较小的数试验一下后发现这个递归终止条件还不足够。

n=2时,f(2)=f(1)+f(0)。如果递归终止条件只有一个f(1)=1,那f(2)就无法求解,递归无法结束。所以除了f(1)=1这一个递归终止条件外,还要有f(0)=1,表示走0个台阶有一种走法,从思维上以及动图上来看,这显得的有点不符合逻辑。所以为了便于理解,把f(2)=2作为一种终止条件,表示走2个台阶,有两种走法,一步走完或者分两步来走。

总结如下:

①假设只有一个台阶,那么只有一种走法,那就是爬1个台阶②假设有两个个台阶,那么有两种走法,一步走完或者分两步来走

递归终止条件

通过递归条件:

11f(1)=1;22f(2)=2;33f(n)=f(n-1)+f(n-2)

很容易推导出递归代码:

11intf(intn){22if(n==1)return1;33if(n==2)return2;44returnf(n-1)+f(n-2);55}

通过上述三个示例,总结一下如何写递归代码:

找到如何将大问题分解为小问题的规律

通过规律写出递推公式

通过递归公式的临界点推敲出终止条件、

将递推公式和终止条件翻译成代码

什么是动态规划

介绍动态规划之前先介绍一下分治策略(DivideandConquer)。

分治策略

将原问题分解为若干个规模较小但类似于原问题的子问题(Divide),「递归」的求解这些子问题(Conquer),然后再合并这些子问题的解来建立原问题的解。

因为在求解大问题时,需要递归的求小问题,因此一般用「递归」的方法实现,即自顶向下。

动态规划(DynamicProgramming)

动态规划其实和分治策略是类似的,也是将一个原问题分解为若干个规模较小的子问题,递归的求解这些子问题,然后合并子问题的解得到原问题的解。

区别在于这些子问题会有重叠,一个子问题在求解后,可能会再次求解,于是我们想到将这些子问题的解存储起来,当下次再次求解这个子问题时,直接拿过来就是。

将「动态规划」的概念关键点抽离出来描述就是这样的:

1.动态规划法试图只解决每个子问题一次2.一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。

从递归到动态规划

相同颜色代表着爬台阶问题在递归计算过程中重复计算的部分。

通过图片可以发现一个现象,我们是自顶向下的进行递归运算,比如:f(n)是f(n-1)与f(n-2)相加,f(n-1)是f(n-2)与f(n-3)相加。

思考一下:如果反过来,采取自底向上,用迭代的方式进行推导会怎么样了?

下面通过表格来解释f(n)自底向上的求解过程。

台阶数123456789走法数12

表格的第一行代表了楼梯台阶的数目,第二行代表了若干台阶对应的走法数。其中f(1)=1和f(2)=2是前面明确的结果。

第一次迭代,如果台阶数为3,那么走法数为3,通过f(3)=f(2)+f(1)得来。

第二次迭代,如果台阶数为4,那么走法数为5,通过f(4)=f(3)+f(2)得来。

由此可见,每一次迭代过程中,只需要保留之前的两个状态,就可以推到出新的状态。

showmethecode

11intf(intn){22if(n==1)return1;33if(n==2)return2;44//a保存倒数第二个子状态数据,b保存倒数第一个子状态数据,temp保存当前状态的数据55inta=1,b=2;66inttemp=a+b;77for(inti=3;i<=n;i++){88temp=a+b;99a=b;1010b=temp;1111}1212returntemp;1313}

程序从i=3开始迭代,一直到i=n结束。每一次迭代,都会计算出多一级台阶的走法数量。迭代过程中只需保留两个临时变量a和b,分别代表了上一次和上上次迭代的结果。为了便于理解,引入了temp变量。temp代表了当前迭代的结果值。

详解动态规划

「动态规划」中包含三个重要的概念:

【最优子结构】【边界】【状态转移公式】

在「爬台阶问题」中

f(10)=f(9)+f(8)是【最优子结构】f(1)与f(2)是【边界】f(n)=f(n-1)+f(n-2)【状态转移公式】

「爬台阶问题」只是动态规划中相对简单的问题,因为它只有一个变化维度,如果涉及多个维度的话,那么问题就变得复杂多了。

难点就在于找出「动态规划」中的这三个概念。

比如「国王和金矿问题」。

国王和金矿问题

有一个国家发现了5座金矿,每座金矿的黄金储量不同,需要参与挖掘的工人数也不同。参与挖矿工人的总数是10人。每座金矿要么全挖,要么不挖,不能派出一半人挖取一半金矿。要求用程序求解出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?

5座金矿

找出「动态规划」中的这三个概念

国王和金矿问题中的【最优子结构】

国王和金矿问题中的【最优子结构】有两个:

①4金矿10工人的最优选择②4金矿(10-5)工人的最优选择

4金矿的最优选择与5金矿的最优选择之间的关系是

MAX[(4金矿10工人的挖金数量),(4金矿5工人的挖金数量+第5座金矿的挖金数量)]

国王和金矿问题中的【边界】

国王和金矿问题中的【边界】有两个:

①当只有1座金矿时,只能挖这座唯一的金矿,得到的黄金数量为该金矿的数量②当给定的工人数量不够挖1座金矿时,获取的黄金数量为0

国王和金矿问题中的【状态转移公式】

我们把金矿数量设为N,工人数设为W,金矿的黄金量设为数组G[],金矿的用工量设为数组P[],得到【状态转移公式】:

边界值:F(n,w)=0(n<=1,w

F(n,w)=g[0](n==1,w>=p[0])

F(n,w)=F(n-1,w)(n>1,w

F(n,w)=max(F(n-1,w),F(n-1,w-p[n-1])+g[n-1])(n>1,w>=p[n-1])

国王和金矿问题中的【实现】

先通过几幅动画来理解「工人」与「金矿」搭配的方式

1.只挖第一座金矿

只挖第一座金矿

在只挖第一座金矿前面两个工人挖矿收益为零,当有三个工人时,才开始产生收益为200,而后即使增加再多的工人收益不变,因为只有一座金矿可挖。

在第一座与第二座金矿这种情况中,前面两个工人挖矿收益为零,因为W<3,所以F(N,W)=F(N-1,W)=0。

当有三个工人时,将其安排挖第一个金矿,开始产生收益为200。

当有四个工人时,挖矿位置变化,将其安排挖第二个金矿,开始产生收益为300。

当有五、六个工人时,由于多于四个工人的人数不足以去开挖第一座矿,因此收益还是为300。

当有七个工人时,可以同时开采第一个和第二个金矿,开始产生收益为500。

3.挖前三座金矿

这是「国王和金矿」问题中最重要的一个动画之一,可以多看几遍

挖前三座金矿

4.挖前四座金矿

挖前四座金矿

国王和金矿问题中的【规律】

仔细观察上面的几组动画可以发现:

对比「挖第一座与第二座金矿」和「挖前三座金矿」,在「挖前三座金矿」中,3金矿7工人的挖矿收益,来自于2金矿7工人和2金矿4工人的结果,Max(500,300+350)=650;

对比「挖前三座金矿」和「挖前四座金矿」,在「挖前四座金矿」中,4金矿10工人的挖矿收益,来自于3金矿10工人和3金矿5工人的结果,Max(850,400+300)=850;

THE END
1.什么是递归Fib(0) = 1 [基本情况] Fib(1) = 1 [基本情况] 对所有n \u003e 1的整数:Fib(n) = (Fib(n-1) + Fib(n-2)) [递归定义] 尽管有许多数学函数均可以递归表示,但在实际应用中,递归定义的高开销往往会让人望而却步。例如: 阶乘(1) = 1 [基本情况] 对所有n \u003e 1的整数:阶乘(n) = (nhttps://xue.baidu.com/okam/pages/strategy-tp/index?strategyId=141402735028627&source=natural
2.递归与迭代的区别递归(recursion):递归常被用来描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A) 迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B) 递归是一个树结构,从字面可以其理解为重复“递推”和“回归”的过程,当“递推https://www.jianshu.com/p/32bcc45efd32
3.递推和递归的区别是什么王利头递推和递归都是强大的编程技术,但它们在解决问题的方式和应用上存在着差异。对于需要以高效方式求解较大https://www.wanglitou.cn/question/di-tui-he-di-gui-de-shi
4.递推和递归的区别递推和递归的区别 1、实现方式不同:递推是通过循环来实现的,递归是通过函数调用来实现的。2、运行效率不同:递推可以避免函数调用层级过深的问题,运行效率比递归高,递归会导致函数调用的层级过深,从而导致栈溢出等问题。3、代码可读性不同:递推的代码比较冗长,但结构清晰,易于理解和调试,递归的代码比较简洁易懂,https://m.51dongshi.com/eedfrceddaecc.html
5.递归与递推的异同勇泽递推和递归有着很多的相似之处,递推甚至可以看做是递归的反方向,但对比其细节是存在很多不同的。递归法:把问题转化为规模更小的子问题解决,思考的重点在于建立原问题和子问题之间的联系。有的问题有很明确的递归结构,但是需要仔细的思考,才能正确的转化为结构相同的子https://www.cnblogs.com/yongze103/archive/2010/10/20/1856352.html
6.python递推python递推和递归的区别小屁孩的技术博客python递推 python递推和递归的区别 Python杂项知识 主要内容: 函数递归 二分法 三元表达式 列表推导式 字典推导式 匿名函数 常用的内置函数 1. 函数递归 函数递归的本质就是一个自己调用自己的过程,直到找到结果后然后返回。递归通常可以分为2个阶段,回溯和递推。所谓回溯就是指一层一层往下回溯,回溯的过程中是将https://blog.51cto.com/u_93011/6807083
7.递推算法和递归算法有什么区别问答递推像是多米诺骨牌,根据前面几个得到后面的;递归是大事化小,比如汉诺塔(Hanoi)问题,典型的递归。https://developer.aliyun.com/ask/125936
8.递归,递推,迭代的区别递推递归区别递归,递推,迭代的区别 #include<iostream> #include<windows.h> using namespace std; 递归: 1、程序调用自身的编程技巧称为递归,是函数自己调用自己。 2、使用递归要注意的有两点: 1)递归就是在过程或函数里面调用自身; 2)在使用递归时, 必须有一个明确的递归结束条件, 称为递归出口.https://blog.csdn.net/weizhengbo/article/details/61053373
9.李涛听从内心,无问西东!电子科技大学主页平台管理系统·intshort intlong int是根据编译环境的不同,所取范围不同。 ·而其中short int和long int至少是表中所写范围,但是int在表中是以16位编译环境写的取值范围。 ·另外c语言int的取值范围在于他占用的字节数,不同的编译器,规定是不一样。 ·ANSI标准定义int是占2个字节,TC是按ANSI标准的,它的int是占2个字节https://faculty.uestc.edu.cn/LiTao_LoVe/zh_CN/article/290136/content/2454.htm
10.递归,递推的意思递归,递推是什么意思递归,递推的近义词沪江在线词典网为您精选递归,递推的意思及读音、递归,递推是什么意思、反义词、近义词等信息,由sissiray于2016年4月11日添加。 读音: 注音: 基本解释: 基本解释 ◎ 递归,递推 dìguī,dìtuī [recursion] 按照某一包含有限步数的法则或公式对一个或多个前面的元素进行运算,以确定一系列元素(如数或函数)的方法https://www.hujiang.com/cidian/297427/
11.数列·递推·递归《数列·递推·递归》是该丛书中的一种.它从数列的概念和最基本的数列——等差数列和等比数列研究开始,分别 对与等差数列、等比数列有关的差分数列、等比差数列、循环 数列、分群数列等进行研究,特别是对数列求和以及数列不等 式的种种问题进行了详细地归纳研究。《数列https://baike.sogou.com/v76363095.htm