漫谈递归和迭代

 

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

与重复密切相关的是递归,在递归技术中,概念是直接或间接由其自身定义的。例如,咱们能够经过“表要么为空,要么是一个元素后面再跟上一个表”这样的描述来定义表。不少编程语言都支持递归。在C语言中,函数F是能够调用自身的,既能够从F的函数体中直接调用本身,也能够经过一连串的函数调用,最终间接调用F。另外一个重要思想——概括,是与“递归”密切相关的,并且经常使用于数学证实中。github

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

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

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

递归分为两个阶段:编程语言

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

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

斐波那契数列spa

 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

相关文章
相关标签/搜索