漫谈递归和迭代

递归(recursion)在计算机科学中是指一种通过重复将问题分解为同类问题的子问题而解决问题的方法。可以极大地减少代码量。递归的能力在于用有限的语句来定义对象的无限集合。递归式方法可以被用于解决很多计算机科学问题,因此它是计算机科学中十分重要的一个概念。绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归可以完全取代循环,因此在很多函数编程语言中习惯用递归来实现循环。

与重复密切相关的是递归,在递归技术中,概念是直接或间接由其自身定义的。例如,我们可以通过“表要么为空,要么是一个元素后面再跟上一个表”这样的描述来定义表。很多编程语言都支持递归。在C语言中,函数F是可以调用自身的,既可以从F的函数体中直接调用自己,也可以通过一连串的函数调用,最终间接调用F。另一个重要思想——归纳,是与“递归”密切相关的,而且常用于数学证明中。

使用递归要注意的有两点:

1)递归就是在过程或函数里调用自身;

2)在使用递归时,必须有一个明确的递归结束条件,称为递归出口。

递归分为两个阶段:

1)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解;

2)回归:当获得最简单的情况后,逐步返回,依次得到发杂的解。

斐波那契数列

 1 int fib(int n)
 2
 3 {
 4
 5    if(0 == n)
 6
 7        return 0;
 8
 9    if(1 == n)
10
11        return 1;
12
13    if(n > 1)
14
15        return fib(n-1)+fib(n-2);
16
17 }  

上面就是一个简单的递归调用了,由于递归引起一系列的函数调用,并且有可能会有一系列的重复计算,递归算法的执行效率相对较低。

递归调用实际上是函数自己在调用自己,而函数的调用开销是很大的,系统要为每次函数调用分配存储空间,并将调用点压栈予以记录。而在函数调用结束后,还要释放空间,弹栈恢复断点。所以说,函数调用不仅仅浪费空间,还浪费时间。

迭代(interation)是程序中对一组指令(或一定步骤)的重复。它即可以用作通用的术语(与“重复”同义),也可以用来描述一种特定形式的具有可变状态的重复。

计算机的威力源自其反复执行同一任务或同一任务不同版本的能力。在计算领域,迭代这一主题会以多种形式出现。数据模型中的很多概念(比如表)都是某种形式的重复,比如“表要么为空,要么由一个元素接一个元素,再接一个元素,如此往复而成”。使用迭代,程序和算法可以在不需要单独指定大量相似步骤的情况下,执行重复性的任务,如“执行下一步骤1000次”。编程语言使用像C语言中的while语句和for语句那样的循环结构,来实现迭代算法。

相比迭代,用递归解决这些问题来的更轻松,别人理解起你的代码也更加容易。但是递归有它自身的问题,每一次递归基本都需要在栈上申请一块新的空间,如果你干得漂亮的话用一个递归爆掉一个栈也不是很难的事情,除此之外,个人认为递归相对于迭代来说和计算机本身的设计原理有些不搭,同样的功能递归应该要慢一些。

有一种计算阶乘的方式,这里使用递归函数定义了计算阶乘的函数:

1 func factorial(n: Int) -> Int {
2     if n == 0 {
3         return 1
4     }
5     return n * factorial(n - 1)
6 }

现在我们试着描述这个函数的计算过程,以factorial(5)为例,一步步代换其计算过程。我们可以看到一个先逐步展开而后收缩的形状。在展开阶段里,这一计算过程构造起一个推迟进行的操作所形成的链条(在这里是一个乘法的链条),收缩过程表现为这些运算的实际执行。其形状可以描绘为如下的图例:

 1 (factorial 5)
 2 (5 * (factorial 4))
 3 (5 * (4 * (factorial 3))
 4 (5 * (4 * (3 * (factorial 2))
 5 (5 * (4 * (3 * (2 * (factorial 1)))
 6 (5 * (4 * (3 * (2 * 1))))
 7 (5 * (4 * (3 * 2)))
 8 (5 * (4 * 6))
 9 (5 * 24)
10 120

这样的计算过程是一个递归计算过程。递归计算过程由一个推迟执行的运算链条刻画,要执行递归计算过程,解释器就需要维护好那些以后要执行的操作的轨迹。

这种不同对于计算机而言却是重要的。在迭代的情况里,计算过程的任何一点,固定数目的状态变量都提供了有关计算状态的一个完整描述。而描述一个递归计算过程,需要一些“隐含”信息,它们并未保存在程序变量里,而是由解释器维持着,指明了在所推迟的运算所形成的链条里,计算过程正处于何处(这种解释器维持运算链条,需要使用一种称为栈的数据结构)。这个链条越长,需要保存的信息也就越多。

递归计算过程,通常容易理解,符合人类的思维习惯。但由于需要使用栈机制实现,其空间复杂度通常很高。对于一些递归层数深的计算,计算机会力不从心,空间上会以内存崩溃而告终。而且递归也带来了大量的函数调用,这也有许多额外的时间开销。所以在深度大时,它的时间复杂度和空间复杂度就都不好了。

迭代算法是用计算机解决问题的一种基本方法。它利用计算机运算速度快,适合做重复性操作的特点,让计算机对一组命令(或一定步骤)进行重复执行,在每次执行这组命令(或步骤)时,都从变量的原值退出它的一个新值。利用迭代算法解决问题,需要做好以下三个方面的工作:

(1)确定迭代变量。在可以用迭代算法解决的问题中,至少存在一个直接或间接地不断由旧值递推出新值的变量,这个变量就是迭代变量。

(2)建立迭代关系。所谓迭代关系,指如何从变量的前一个值推出其下一个值的公式(或关系)。迭代关系式的建立是解决问题的关键,通常可以使用递推或倒推的方法来完成。

(3)对迭代过程进行控制。在什么时候结束迭代过程?这是编写迭代程序必须考虑的问题。不能让迭代过程无休止地重复执行下去。迭代过程的控制通常可分为两种情况:一种是所需的迭代次数是个确定的值,可以计算出来;另一种是所需的迭代次数无法确定。对于前一种情况,可以构建一个固定次数的循环来实现对迭代过程的控制;对于后一种情况,需要进一步分析出用来结束迭代过程的条件。

递归是设计和描述算法的一种有力的工具,能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。

递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib(n),必须先计算fib(n-1)和fib(n- 2),而计算fib(n-1)和fib(n-2),又必须先计算fib(n-3)和fib(n-4)。依次类推,直至计算fib(1)和fib(0),分别能立即得到结果1和0。在递推阶段,必须要有终止递归的情况。例如在函数fib中,当n为1和0的情况。

在回归阶段,当获得最简单情况的解后,逐级返回,依次得到稍复杂问题的解,例如得到fib(1)和fib(0)后,返回得到fib(2)的结果,……,在得到了fib(n-1)和fib(n-2)的结果后,返回得到fib(n)的结果。

在编写递归函数时要注意,函数中的局部变量和参数知识局限于当前调用层,当递推进入“简单问题”层时,原来层次上的参数和局部变量便被隐蔽起来。在一系列“简单问题”层,它们各有自己的参数和局部变量。

由于递归引起一系列的函数调用,并且可能会有一系列的重复计算,递归算法的执行效率相对较低。当某个递归算法能较方便地转换成递推算法时,通常按递推算法编写程序。例如上例计算斐波那契数列的第n项的函数fib(n)应采用递推算法,即从斐波那契数列的前两项出发,逐次由前两项计算出下一项,直至计算出要求的第n项。

参考

http://note.zqguo.com/archives/301

http://www.ituring.com.cn/tupubarticle/5504#

http://lincode.github.io/Recursion-Iteration/

http://www.bianceng.cn/Programming/sjjg/200901/11200.htm

时间: 2024-08-05 23:01:56

漫谈递归和迭代的相关文章

递归和迭代两种方式实现归并排序(Java版)

递归版 package MergeSort; import Utils.SortUtils; /** * 归并排序递归版 * @author liguodong */ public class Demo02 { public static void mergeSort(int[] a){ mSort(a, a, 0, a.length-1); } /** * * @param SR为待排序的数据 * @param TR1为排序之后的数据 * @param s * @param t */ publ

递归和迭代(Recursion and Iteration)

递归 特点:简而言之,递归就是应用程序调用自身.所以,存在预期收敛,才能使用递归(因为不能无限期递归调用下去). 优点:程序看着比较简单,比较容易实现. 缺点:递归要占用额外的栈空间,如果递归的深度比较大,那么占用的栈比较多,而且调用函数的时间也比较多,时空性都不好. 所以选择递归要考虑好处和缺点之间的权衡. 迭代 特点:通过步号寻找需要的信息,经典的例子比如C++中的for循环语句(迭代遍历程序). 优点:开销只因循环的增加而相应增加,没有额外的空间开销和时间开销. 缺点:编写复杂问题时可能程

递归与迭代【转】

1 递归的基本概念:程序调用自身的编程技巧称为递归,是函数自己调用自己. 一个函数在其定义中直接或间接调用自身的一种方法,它通常把一个大型的复杂的问题转化为一个与原问题相似的规模较小的问题来解决,可以极大的减少代码量.递归的能力在于用有限的语句来定义对象的无限集合. 1.1 使用递归要注意的有两点: 1)递归就是在过程或函数里面调用自身: 2)在使用递归时,必须有一个明确的递归结束条件,称为递归出口. 1.2 递归分为两个阶段: 1)递推:把复杂的问题的求解推到比原问题简单一些的问题的求解: 2

剑指offer (9) 递归和迭代 斐波那契数列

通常基于递归实现的代码比基于循环实现的代码要简洁很多 比如 二叉树遍历以及 二叉树的许多操作 递归由于是函数调用自身,每一次函数调用,都需要在内存栈中分配空间以保存参数.返回地址以及临时变量 而每个进程的栈容量是有限的,当递归调用的层级太多时,就会导致 调用栈溢出 递归有时伴随大量重复的计算, 二叉树遍历的递归操作不存在重复计算,因为每个结点的左右子树是严格区分开的 例如求解 斐波那契数列: 解题分析 int fib(int n) { assert(n >= 0); int prevTwo =

【万字博文】分析与设计:插入排序和分治算法、递归和迭代的探讨

插入排序及其解决思路 算法的作用自然不用多说,无论是在校学生,还是已经工作多年,只要想在计算机这条道路走得更远,算法都是必不可少的. 就像编程语言中的"Hello World!"程序一般,学习算法一开始学的便是排序算法.排序问题在日常生活中也是很常见的,说得专业点: 输入是:n个数的一个序列<a1,a2,...,an?1,an> 输出是:这n个数的一个全新的序列<a,1,a,2,...,a,n?1,a,n>,其特征是a,1≤a,2≤...≤a,n?1≤a,n 举

递归与迭代

头文件 #include <stdlib.h> #include <stdio.h> #include <string.h> #pragma once 代码文件 #include "myH.h" //深度理解递归与迭代方法 //递归与迭代方法的区别: //递归使用函数和条件语句(if和else语句) //迭代法使用循环语句(for和while语句) /**************************************************

【基础服务】简单理解DNS的递归、迭代查询 - DNS(一)

DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串.通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析).DNS协议运行在UDP协议之上,使用端口号53.在RFC文档中RFC 2181对DNS有规范说明,RFC 2136对DNS的动态更新进行说明,RFC 2308对DNS查询的反向缓存进行说明. 简单理解DNS的递归.迭代查询过程: 客户端发

C自学笔记-递归与迭代的使用方法笔记与两者的使用场合

递归和迭代在刚开始学C语言的时候就学过,但是不知道怎么使用.今天遇到一个题目分析过后 我瞬间想起来之前学过递归的方法,做完题后顺便翻了翻书整理了这个笔记.题目大概是这样的. 题目:猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个  第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下  的一半零一个.到第10天早上想再吃时,见只剩下一个桃子了.求第一天共摘了多少. 实力分析一波: 1:1 2:(1+1)*2 3:(n2+1)*2 10:(n9+1

【万字总结】探讨递归与迭代的区别与联系及如何求解10000的阶层

递归和迭代 这两个概念也许很多童鞋依旧分不清楚,下面通过求解斐波那契数来看看它们俩的关系吧. 斐波那契数的定义: f0=0 f1=1 fi=fi?1+fi?2(i>1) 递归: (factorial 6) (* 6 (factorial 5)) (* 6 (* 5 (factorial 4))) (* 6 (* 5 (* 4 (factorial 3)))) (* 6 (* 5 (* 4 (* 3 (factorial 2))))) (* 6 (* 5 (* 4 (* 3 (2 (factori