这个递归递归定义和循环定义的区别放在一起好难理解,谁能帮我理解理解呀


非递归效率高;递归代码写出来思路清晰可读性强。

生成可执行文件大小应该和编译器有关吧。。

递归的话函数调用是有开销的而且递归的次数受堆栈大小的限淛。 
以二叉树搜索为例: 

如果这个二叉树很庞大反复递归函数调用开销就很大,万一堆栈溢出怎么办 
现在我们用循环改写: 


递归好处:代码更简洁清晰,可读性更好 

递归可读性好这一点对于初学者可能会反对。实际上递归的代码更清晰但是从学习的角度要理解递归嫃正发生的什么,是如何调用的调用层次和路线,调用堆栈中保存了什么可能是不容易。但是不可否认递归的代码更简洁一般来说,一个人可能很容易的写出前中后序的二叉树遍历的递归算法要写出相应的非递归算法就比较考验水平了,恐怕至少一半的人搞不定所以说递归代码更简洁明了。 

递归坏处:由于递归需要系统堆栈所以空间消耗要比非递归代码要大很多。而且如果递归深度太大,可能系统撑不住 

楼上的有人说: 
太复杂了就递归吧,,免得循环看不懂 

      话虽然简单,其实非常有道理:对于小东西能用循环干嘛要折腾?如果比较复杂在系统撑的住的情况下,写递归有利于代码的维护(可读性好) 

      另:一般尾递归(即最后一句话进行递归)和单向递归(函数中只有一个递归调用地方)都可以用循环来避免递归,更复杂的情况则要引入栈来进行压栈出栈来改造成非递归这个栈不一定要严格引入栈数据结构,只需要有这样的思路用数组什么的就可以。

至于教科书上喜欢n!的示例我想只是便于递归思路的引进和建立。真正莋代码不可能的


循环方法比递归方法快, 因为循环避免了一系列函数调用和返回中所涉及到的参数传递和返回值的额外开销。 

递归递归定義和循环定义的区别之间的选择一般情况下, 当循环方法比较容易找到时, 你应该避免使用递归。这在问题可以按照一个递推关系式来描述時, 是时常遇到的, 比如阶乘问题就是这种情况反过来, 当很难建立一个循环方法时, 递归就是很好的方法。实际上, 在某些情形下, 递归方法总是顯而易见的, 而循环方法却相当难找到当某些问题的底层数据结构本身就是递归时, 则递归也就是最好的方法了。


递归其实是方便了程序员難为了机器它只要得到数学公式就能很方便的写出程序。优点就是易理解容易编程。但递归是用栈机制实现的(c++)每深入一层,都偠占去一块栈数据区域对嵌套层数深的一些算法,递归会力不从心空间上会以内存崩溃而告终,而且递归也带来了大量的函数调用這也有许多额外的时间开销。所以在深度大时它的时空性就不好了。

循环其缺点就是不容易理解编写复杂问题时困难。优点是效率高运行时间只因循环次数增加而增加,没什么额外开销空间上没有什么增加。


递归算法与迭代算法的设计思路区别在于:函数或算法是否具备收敛性当且仅当一个算法存在预期的收敛效果时,采用递归算法才是可行的否则,就不能使用递归算法

当然,从理论上说所有的递归函数都可以转换为迭代函数,反之亦然然而代价通常都是比较高的。

但从算法结构来说递归声明的结构并不总能够转换为迭代结构,原因在于结构的引申本身属于递归的概念用迭代的方法在设计初期根本无法实现,这就像动多态的东西并不总是可以用静多態的方法实现一样这也是为什么在结构设计时,通常采用递归的方式而不是采用迭代的方式的原因一个极典型的例子类似于链表,使鼡递归定义及其简单但对于内存定义(数组方式)其定义及调用处理说明就变得很晦涩,尤其是在遇到环链、图、网格等问题时使用迭代方式从描述到实现上都变得很不现实。


(1). 通过分析跳过分解过程,直接用循环结构的算法实现求解过程

(2). 自己用栈模拟系统的运行时栈,通过分析只保存必须保存的信息从而用非递归算法替代递归算法。

(3). 利用栈保存参数由于栈的后进先出特性吻合递归算法的执行过程,洇而可以用非递归算法替代递归算法

        2. 把程序要完成的工作分成两类:手头工作和保存在栈中的待完成的工作。手头工作指程序正在做的笁作由于某些工作 
        不能一步完成,必须暂缓完成于是可把它保存在栈中,这就是待完成的工作

        3. 手头工作必须有其结束条件,不能永遠做下去;保存的待完成工作必须含有完成该项工作的所有必要信息

        4. 程序必须有秩序地完成各项工作。如可把手头工作恰当处理(直接处理或暂时保存)后,才能继续接手下一步的工作 

// 即下次的手头工作必须从栈中取得


例2. 后根序遍历二叉树 


递归算法向非递归算法转換

递归算法实际上是一种分而治之的方法,它把复杂问题分解为简单问题来求解对于某些复杂问题(例如hanio塔问题),递归算法是一种自然且匼乎逻辑的解决问题的方式但是递归算法的执行效率通常比较差。因此在求解某些问题时,常采用递归算法来分析问题用非递归算法来求解问题;另外,有些程序设计语言不支持递归这就需要把递归算法转换为非递归算法。

    将递归算法转换为非递归算法有两种方法一种是直接求值,不需要回溯;另一种是不能直接求值需要回溯。前者使用一些变量保存中间结果称为直接转换法;后者使用栈保存中间结果,称为间接转换法下面分别讨论这两种方法。

直接转换法通常用来消除尾递归和单向递归将递归结构用循环结构来替代。

尾递归是指在递归算法中递归调用语句只有一个,而且是处在算法的最后例如求阶乘的递归算法:

当递归调用返回时,是返回到上一層递归调用的下一条语句而这个返回位置正好是算法的结束处,所以不必利用栈来保存返回信息。对于尾递归形式的递归算法可以利用循环结构来替代。例如求阶乘的递归算法可以写成如下循环结构的非递归算法:

单向递归是指递归算法中虽然有多处递归调用语句泹各递归调用语句的参数之间没有关系,并且这些递归调用语句都处在递归算法的最后显然,尾递归是单向递归的特例例如求斐波那契数列的递归算法如下:

对于单向递归,可以设置一些变量保存中间结构将递归结构用循环结构来替代。例如求斐波那契数列的算法中鼡s1和s2保存中间的计算结果非递归函数如下:

该方法使用栈保存中间结果,一般需根据递归函数在执行过程中栈的变化得到其一般过程洳下:

  退栈,将栈顶元素赋给s;   if (s是要找的结果) 返回;    寻找到s的相关状态s1;

间接转换法在数据结构中有较多实例如二叉树遍历算法嘚非递归实现、图的深度优先遍历算法的非递归实现等等。

使用非递归方式实现递归问题的算法程序,不仅可以节省存储空间,而且可以极大哋提高算法程序的执行效率本文将递归问题分成简单递归问题和复杂递归问题;简单递归问题的非递归实现采用递推技术加以求解,复杂递歸问题则根据问题求解的特点采用两类非递归实现算法,使用栈加以实现。

}

递归是最强大的编程方法之一咜在程序员工具箱中的有用工具清单上名列前茅,能够以令人震惊的少量代码解决极其复杂的问题然而,递归通常是一个难以理解的概念因为它需要从非标准的角度来看待命令是如何处理的。

本文将从简单的示例开始逐步过渡到更具挑战性的示例,并附有大量图表:

遞归的思维方式递归关系记忆化分治法策略递归是一种解决问题的方法其中,函数在函数定义内调用自身每个递归实现都需要有两个え素:

一个或多个 Base Case(边界条件、基准条件),它们是终止条件(Terminating Case)在这些条件中,不需要用更多的递归来进一步寻找答案一组规则(遞归关系),通过启动另一轮递归将其他条件减少到一个 Base Case。例如让我们考虑反向打印字符串。输入 “hello” 的输出应为 “olleh”完成这一任務的贴袋方法是使用

循环,并打印出从最后一个索引到第一个索引的每个字符

递归方法将首先创建一个函数

,它接受一个字符串作为参數如果输入的长度不为 0(则将作为 Base Case 或终止条件),我们将打印最后一个字母并在当前字符串上启动另一个

示例,但不包括最后一个字苻串(因为它刚刚被打印)

请注意,因为该函数是在其内部调用的所以它自己创建了一个

循环。此外在调用该函数的另一个实例之湔必须存在

错误,因为脚本认为这个无线循环没有结束这类似于

让我们看看这个递归函数在 “hello” 上是如何起作用的:

在比较复杂的问题仩进行递归是一件很困难的事情,因为确定它的两个组成部分——递归关系即一个问题的结果与其子问题的结果之间的关系;在 Base Case 下,则昰无需任何递归调用就可以直接计算的情况有时,Base Case 又称为 Bottom Case因为在这种情况下,问题已经缩小到最小的规模

例如,杨辉三角形(Pascal's triangle)中其中每个数字都是它上面两个数字的和,三角形边上都有数字如何使用递归来查找点(i,j)上任何值的值?递归关系的 Base/Terminatin Case 是什么

递归关系可鉯用下面的公式来表示:

这在观察这个三角形的图时,是显而易见的这个公式更好的地方在于,如果我们继续用这个公式把任意位置(i,j)分解为另外两个位置的和那么就不可避免会导致 Base Case——1。杨辉三角形从 1 开始从 1 的和开始,一个完整的复杂图案就出现了

首先,让我们找箌一组规则确定何时满足 Base Case:单元格的值等于 1。注意1 在两种情况下出现:要么位于第一列(j=0),要么位于对角线(i=j)上

现在实现起来很简单,洳果满足 Base Case则返回基值 (1)。否则我们将继续减少问题,直到达到一个 Base Case我们已经确定任何输入都将不可避免地被分解成 Base Case。

到现在为止你應该已经领悟到递归之美了。在本文中我们实际上用五行代码创建了一个自构造树(self-building tree)(如果你想缩短它,甚至也可以是三行代码)當我们两次调用

函数时,启动了两个搜索分支每个分支又启动另外两个,假设它们没有达到 Base Case

递归是如何有效地工作的,这可能有点不鈳思议、令人困惑让我们来通过一个例子来了解递归算法是如何运行的。

根据我们的递归关系(4,2) 被分解成它上面的两个数 (3,1) 和 (3,2)。请注意算法实际上并不知道这些单元格的值是 3,它所做的只是记录它们的位置我们并不知道也不关心任何值,除非它能满足我们的 Base Case根据我们嘚 Base Case (1),我们可以计算其他非基准位置但必须首先找到所有的 Base Case。

根据我们的递归关系Case 被迭代分解,知道满足 Base Case(j=0或i=j)由于我们知道这些 Base Case(1) 的徝,因此可以根据 Base Case 填充其他值

当然,这是递归算法如何工作的极其详细的视图可能过于详细了。实际上这三个步骤都不需要编程;咜们是通过脚本自动执行的。就实现方面而言你需要做的就是调用函数本身,并确保它在某些时候必须在 Base Case 下终止

时,我们不会把返回看作一个过程而是将它当做一个数字。由于

会启动自己的分支过程但最终会返回另一个数字(比如 3),因此将它视为后者而非前者是囿帮助的这可能会导致不必要的复杂性和令人头疼的问题。

人们可能会倾向于指出这种递归算法中的一些低效之处毕竟, “6” 被分解為 “3”这两个 “3” 具有相同的分解(就值而言),但它被天真地计算了两次这在递归中很常见,在递归中一个 Case 的 Base Case 已经计算过,但会洅次计算为了解决这一问题,我们使用记忆化

以斐波那契(Fibonacci)数列为例,其中前两个数字是 0 和 1下一个数字是前面两个数字之和。根據之前的知识我们知道 Base Case 是 0 和 1,我们的递归关系是v(i) = v(i-2) + v(i-1)因此,我们可以构造一个简单的递归函数来查找 斐波那契数列在任意索引i处的值从 0 開始。

请注意虽然这是递归的一个巧妙应用,但效率极其低下“8” 被分解为 “3” 和 “5”,而 “5” 又被分解为 “3” 和 “2”我们从头开始计算所有内容,并建立一个完整的搜索树但其中有很多重复项。

使用记忆化我们就可以通过创建缓存来解决这个问题。这可以通过使用字典来实现并存储我们以前看到过的值。例如当索引 6(值为 8)被分为索引 4 和索引 5(值分别为 3 和 5)时,我们就可以将索引 4 的值存储箌缓存中当索引 5 被计算为索引 3 加上索引 4 时,我们可以从缓存中提取索引 4而不是通过构建另一棵树向下扩展到 Base Case 来重新计算索引 4。为了将記忆化添加到我们的功能中我们添加了两个功能:首先,如果当前索引已存储在缓存中我们只需返回存储的值;其次,在我们继续减尐之前应该将值添加到缓存中,以便可以加快进一步的操作请注意,缓存必须是全局变量或者不管命令调用的范围如何,都可以进荇检索和更改该变量

通过添加简单的记忆化,我们的递归函数比以前任何时候都快得多功能也更强大得多。

递归是各种最快排序算法嘚核心排序算法的目的是收集一个数字列表,然后从最小值到最大值返回它们由于许多应用程序都依赖于快速排序,因此寻找一种能夠对列表进行最快排序的算法非常有意义一些最快的算法使用分治法策略的递归方法。

分治法是一种策略在这个策略中,出事问题递歸地分解为多个子问题在每个子问题具有单位大小(类似于 Base Case)之后,将为每个子问题找到子解然后再次递归地将这些子解组合在一起,形成一个完整的解

例如,考虑快速排序(QuickSort)是排序中最快的算法之一如果实现得当,它的执行速度可以比它的竞争对手和前身快 2~3 倍快速排序从选择一个 “基准” (Pivot)开始。在简单的实现中就我们的目的而言,基准的选择是任意的但更专业的实现对所选的基准非瑺谨慎。

所有小于基准的元素移到基准的左侧所有大于基准的元素移到其右侧。请注意这个做操只是部分地解决了这个任务。

通过其基准分割列表的过程称为分区因为基准将列表华为两个分区或边。每个分区在自身上调用另一个分区迭代以此类推,直到一个分区达箌一个 Base Case(1 个单元简单地说,就是一个数字)

在执行足够的递归后,原始列表将被分割得足够多以至于不能再调用递归。在这一点孓解的组成只是将他们横向堆叠在一起。组成的结果是一个排序的列表

请注意,快速排序遵循前面讨论过的许多递归构建块只是在更高级别上。递归关系是组件的分区Base Case 是大小为 1 的分区。从一个原始列表开始递归调用相同的进程(选择基准、分区),直到结果只包含 Base Case

递归是一种在函数内调用自身的编程方法,允许使用最少的代码进行循环和自动构建树在构造任何递归函数时,必须清楚地理解两个え素:递归关系和 Base Case记忆化是一种防止重复操作的方法,它将信息存储在缓存中然后在需要时检索它们。分治法是递归的许多更深层次嘚应用之一它将问题递归地分解为若干子问题(Base Case),从这些子问题中可以很容易地提取和聚合子解从而形成一个完整的解。作者介绍:

Andre YeCritiq 联合创始人,机器学习、计算机科学和数学爱好者Medium 编辑、最佳作家。

}

在数据结构算法设计中或者一個方法的具体实现的时候,有一种方法叫做“递归”这种方法在思想上并不是特别难,但是实现起来还是有一些需要注意的虽然对于佷多递归算法都可以由相应的循环迭代来代替,但是对于一些比较抽象复杂的算法不用递归很难理解与实现
递归分为直接递归和间接递歸,就简单分享一下两个小的直接递归
对于递归的概念,其实你可以简单的理解为自己定义自己记得小时候看过一部电视剧《狼毒花》,里面主角叫做“常发”但是个文盲,老师问他叫什么他说“常发”。“哪个常”“常发的常啊!”“哪个发?”“常发的发啊!”结果第二节课老师就让一群小朋友一起喊“常发的常常发的发,傻瓜的傻傻瓜的瓜”。言归正传显然在多数情况下递归是解释┅个想法或者定义的一种合理方法。在思想上递归类似于数学中曾经学过的数学归纳法
递归的实现要注意有两点:一个递归的选项和一個非递归的选项,后者成为基础情形(base case)基础情形是递归的终结情形,没有基础情形或者处理不好都会导致无穷递归这是我们不想要嘚结果。递归实现起来最关键的是处理好基础情形 结合具体事例在说一下递归回溯的过程。
1、爬楼梯算法:已知一个楼梯有n个台阶每佽可以选择迈上一个或者两个台阶,求走完一共有多少种不同的走法
递归函数有返回值的比没有返回值的麻烦一点,因为一个函数只有┅个返回值但是递归还要求有基础情形的存在,所以还必须有if判断来终止递归所以在每一个if或者else后边都有一个return,这样保证函数在任何┅种情况下都有且仅有一个返回值
A:如果有0个台阶,那么有0种走法这个不用多说;
B:如果有1个台阶,那么有1种走法;
C:如果有2个台阶那么有2种走法(一次走1个,走两次;一次走两个);
以上的B和C就是基础情形
D:接下来就是递归了,如果台阶数目多于2个那么首先第┅步就有两种选择:第一次走1个,或者第一次走两个这样除了第一次后边的走法就有了两种情形:climbStairs(n-1)和climbStairs(n-2)。这样一直递归下去直到出现到叻基础情形(即n=1或n=2的情形),递归到这个地方(基础情形)然后开始回溯 ,这就是所说的和递归密切相关的“回溯”了回溯,顾名思義就是从结果倒着回去找到整个过程,进而分析这个路径或者说是实现的过程

需要注意的是,这个算法实现思路上简单但是复杂度並没有降低,还牵扯回溯保存堆栈问题(其实递归的设计尽量避免这种嵌套两个的递归方式(climb(n)中包含climb(n-1)和climb(n-2))这种操作会使得堆棧开辟空间随着n的增大以指数型增长,最终程序很容易崩溃)而且在台阶数目多到一定数量的时候会越界(走法次数会超出int的范围),所以递归程序很大程度上就是思想实现设计上简单理解一些

然后还有几个比较典型的递归问题:比如说迷宫问题,或者最经典的汉诺塔問题下边都给出源码,大家一块儿学习一下

汉诺塔问题:一次只能移动一个盘子;不能把大盘子放在小盘子上;除去盘子在两个柱子の间移动的瞬间,盘子必须都在柱子上(在这三点要求下把盘子从起始柱子A全部移动到目标柱子C上)
基础情形:n==1的时候终止递归,进行囙溯

迷宫走法:二维数组构成一个迷宫,1表示通路0表示不通,找到一条路径从起始点(traverse函数的参数)到终点(右下角点)


 

还有一个⑨连环的操作,有兴趣的话可以一起看看
如有不得当之处,还望诸位大神指教!

}

我要回帖

更多关于 递归定义和循环定义的区别 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信