分类: IT业界
2013-09-11 21:19:09
归与迭代都是基于控制结构:迭代用重复结构,而递归用选择结构。递归与迭代都涉及重复:迭代显式使用重复结构,而递归通过重复函数调用实现重复。递归与迭代都涉及终止测试:迭代在循环条件失败时终止,递归在遇到基本情况时终止。使用计数器控制重复的迭代和递归都逐渐到达终止点:迭代一直修改计数器,直到计数器值使循环条件失败;递归不断产生最初问题的简化副本,直到达到基本情况。迭代和递归过程都可以无限进行:如果循环条件测试永远不变成false,则迭代发生无限循环;如果递归永远无法回推到基本情况,则发生无穷递归。
递归有许多缺点,它重复调用机制,因此重复函数调用的开销很大,将占用很长的处理器时间和大量的内存空间。每次递归调用都要生成函数的另一个副本(实际上只是函数变量的另一个副本).从而消耗大量内存空间。迭代通常发生在函数内,因此没有重复调用函数和多余内存赋值的开销。那么,为什么选择递归呢?
---------------------------------------------------------------------------摘要:在算法的分析与设计中,递归和迭代都是特别有力的工具,很多难解的问题都是通过递归或迭代算法解出来的。本文在比较这两种算法在不同情况下的可行性的基础上,阐述了怎样对这两种算法进行有效的选择。
关键词:递归算法 迭代算法 程序
0 引言
在算法分析与设计中,递归与迭代是我们解决循环问题常用的两种方法。那么,在既可以用递归算法又可以用迭代算法解决的问题中,我们究竟该选用哪种算法呢?在程序设计中,我们不但讲求代码所能实现的功能,而且在实现相同功能的同时,更注重优化代码、提高代码的执行效率。这也是我们在选择递归还是迭代思想时考虑的主要因素。
1 递归和迭代概述
如果一个问题刚开始难以解决,可以将其简化后再尝试解决。如果这个过程可以重复进行,问题最终会变得容易处理。由此引出两种不同的方法:递归和迭代。循环或迭代,是一种重复执行一个过程的方法;递归是另一种方法。递归函数是通过调用函数自身来完成任务,而且在每次调用自身时减少任务量。而迭代是循环的一种形式,这种循环不是由用户输入而控制,每次迭代步骤都必须将剩余的任务减少;也就是说,循环的每一步都必须执行一个有限的过程,并留下较少的步骤。循环的进度通常用一个在每次迭代时都进行自增或自减的变量的值来衡量,直到到达预定的目标为止。用递归算法表示许多问题的求解方法时算法思想非常简洁。但是递归算法不仅时间效率非常差,而且由于递归算法是不断的函数调用和函数返回过程,因此其实际的计算机运行时间通常远大于循环方式算法的计算机运行时间,甚至在有限的时间内无法求解。这就存在一个把递归算法化为非递归算法的问题。
2 需要用迭代消解递归的情况
递归算法特别适合于所研究的问题或所处理的数据本身是递归定义的情况。然而,并不意味着这种递归定义保证递归算法是解决该问题的最好方法。事实上,主要是因为拿那种不合适的例子来解释递归算法概念,从而造成了对程序设计中使用递归的普遍怀疑和否定态度,并把递归同低效等同起来。而且在递归算法中,往往会因为追求代码短或者在求解问题时一味追求规律性,多用了无用的压栈和出栈的操作。比如用循环消解的尾递归,是多了无用的压栈和出栈才使速度受损的;斐波那契数列计算的递归改循环迭代所带来的速度大幅提升,是因为改掉了重复计算的毛病。假使一个递归过程中本身包含了大量冗余的操作,并且这个过程又可以用迭代来达到相同的效果。这时,我们就一般用迭代来消解递归。也就是说尾递归算法和单向递归算法可用迭代算法来代替。可以用一个方案来描述人们力图在其中避免使用算法递归的程序,这个方案展示了其构成的模型。(1)式或等价的(2)式就是这个方案:
P≡ if B then (S;P) (1)
P≡(S; if B then P) (2)
要计算用简单递归关系定义的值时,这种方案是很自然的。如下例:
Function F (I: integer ):integer;
Begin if I>0 then F:=I*F(I-1)
Else F:=1;
End (3)
很明显,在这种情况下,递归可由简单迭代代替,即由下面的程序代替。
I:=0;F:=1;
While I
End (4)
一般地,对应于方案(1)或(2)的程序应按照下列方案改写:
P ≡(x:=x0;while B do S)
还有更复杂的递归构造方案,这些方案可以并且应该改写成迭代计算的形式。一个例子就是Fibonacci 数的计算,这些数是按递归关系定义的:
FIBn+1=FIBn+FIBn-1对n>0
而FIB1=1,FIB0=0,一个直接的自然的解法导致程序
function fib (n: integer): integer;
begin if n=0 then fib :=0else
if n=1 then fib :=1 else
fib:=fib(n-1)+fib(n-2)
end
以调用fib(n)来计算FIBn ,就引起这个函数过程的递归活动。频繁程度如何呢?我们注意到,对于n>1,每调用一次引起两个新的调。因此,调用的总次数按指数增长,如下图所示fib(5)的递归树。这样一个程序显然是不实用的。
但显见,利用辅助变量x=FIBi与y=FIBi-1,就可以用迭代方案来计算Fibonicca 数,而避免同一值的重复计算。
{ 对n>0计算x=FIBn}
i:=1;x:=1;y:=0;
while i
x:=x + y; y:=z
end
(注意,对x, y, z的三个赋值,可以只表示成两个赋值,而不需要辅助变量z :x:=x +y ; y:=x-y).由上述例子可以看出,当存在明显的迭代解法时要避免使用递归。
3 不需要消解的递归
那种盲目的消解递归,不惜一切代价躲避递归,认为“递归的速度慢,为了提高速度,必须用栈或者其他的方法来消解”的说法是很片面的。如果一个递归过程用非递归的方法实现后,速度提高了,那只是因为递归做了一些无用功。假使一个递归过程必须要用栈才能消解,那么完全模拟后的结果根本就不会对速度有任何提升,只会减慢;如果你改完后速度提升了,那只证明你的递归函数写的有问题,如多了许多重复操作——打开关闭文件、连接断开数据库,而这些完全可以放到递归外面。可以在本质上是非递归的机器上实现递归过程这一事实本身就证明:为着实际目的,每一个递归程序都可以翻译成纯粹迭代的形式,但这包含着对递归栈的显式处理,而这些运算常常模糊了程序的本质,以致使它非常难以理解。
因此,是递归的而不是迭代的算法应当表述成递归过程。如汉诺塔问题等。汉诺塔问题的递归算法中有两处递归调用,并且其中一处递归调用语句后还有其他语句,因此该递归算法不是尾递归或单向递归。要把这样的递归算法转化为非递归算法,并没有提高程序运行的速度,反而会使程序变得复杂难懂,这是不可取的。也就是说,很多递归算法并不容易改写成迭代程序:它们本质上是递归的,没有简单的迭代形式。这样的递归算法不宜转化为非递归算法。
4 结束语
说到底,在我们选择算法时应该全面分析算法的可行性、效率、代码优化。在综合了算法的各个因素后,选择合适的算法来编写程序,这样的程序才会达到优化的效果。
参考文献:
[1]王晓东.算法分析与设计清华大学出版社.
[2]Alice E. Fischer DavidW .Egger 等著Applied C: An Introduction and More电子工业出版社