递归还是迭代?
如果我们在算法可以使用相同目的的情况下使用循环而不是递归,反之亦然吗?例如:检查给定的字符串是否是回文。
我已经看到许多程序员使用递归来证明简单的迭代算法何时可以胜任。
编译器在决定使用什么方面起着至关重要的作用吗?
解决方案
回答
这取决于语言。在Java中,我们应该使用循环。功能语言优化了递归。
回答
使用递归,每次"迭代"都会导致函数调用的开销,而使用循环,通常只需支付增量/减量。因此,如果循环的代码比递归解决方案的代码复杂得多,则循环通常比递归更好。
回答
我认为在(非尾部)递归中,每次调用函数时都会分配新的堆栈等(这当然取决于语言)会对性能造成影响。
回答
递归在内存中的开销更大,因为每个递归调用通常都需要将一个内存地址压入堆栈,以便以后程序可以返回到该点。
尽管如此,在很多情况下,与使用树一样,与循环相比,递归更为自然和易读。在这些情况下,我建议我们坚持递归。
回答
通常,人们会期望性能损失落在另一个方向上。递归调用可能导致构造额外的堆栈框架;惩罚因人而异。同样,在某些语言(如Python)(更正确的说,在某些语言的某些实现中...)中,对于可能需要递归指定的任务,例如在树数据结构中查找最大值,我们很容易遇到堆栈限制。在这些情况下,我们确实想坚持使用循环。
假设我们有一个优化尾部递归的编译器,编写好的递归函数可以在某种程度上减少性能损失。在。)
除了"边缘"情况(高性能计算,非常大的递归深度等)之外,最好采用能最清楚地表达意图,精心设计和维护的方法。仅在确定需要后进行优化。
回答
它取决于"递归深度"。
它取决于函数调用开销将影响总执行时间的程度。
例如,由于以下原因,以递归方式计算经典阶乘是非常低效的:
数据溢出的风险
堆栈溢出的风险
函数调用开销占执行时间的80%
在开发用于下棋游戏中的位置分析的最小-最大算法时,可以在"分析深度"上递归地实现对后续N个动作的分析(如我正在做的_ ^)
回答
取决于递归函数是否为尾递归(最后一行是递归调用),递归可能会更昂贵。尾递归应该由编译器识别,并针对其迭代对等进行优化(同时保持代码中简洁明了的实现)。
对于那些必须在几个月或者几年内维护代码的可怜的傻瓜(无论是我们本人还是其他人),我将以最有意义,最清晰的方式编写算法。如果遇到性能问题,请先对代码进行概要分析,然后再转向迭代实现,然后再进行优化。我们可能需要研究记忆和动态编程。
回答
使用递归时,性能会下降,因为以任何语言调用方法都意味着大量准备工作:调用代码发布了一个返回地址,调用参数,某些其他上下文信息(例如处理器寄存器)可能会保存在某个位置,并且在返回时被调用的方法发布一个返回值,然后调用者将其取回,并且先前保存的所有上下文信息都将被恢复。迭代和递归方法之间的性能差异在于这些操作花费的时间。
从实现的角度来看,当处理调用上下文所花费的时间与方法执行所花费的时间相当时,我们真的开始注意到这种差异。如果递归方法需要更长的时间才能执行,则调用上下文管理部分,请采用递归方式,因为代码通常更具可读性和易懂性,并且不会引起性能损失。否则,出于效率考虑而进行迭代。
回答
我相信Java中的尾部递归目前尚未优化。在整个有关LtU和相关链接的讨论中,都散布了细节。它可能是即将发布的版本7中的一项功能,但与堆栈检查结合使用时,显然会出现某些困难,因为某些框架可能会丢失。自Java 2以来,堆栈检查已用于实现其细粒度的安全模型。
http://lambda-the-ultimate.org/node/1333
回答
循环可以提高程序的性能。递归可以为程序员提高性能。选择哪种在情况下更重要!
回答
迈克是正确的。 Java编译器或者JVM并未优化尾递归。我们总是会出现类似以下内容的堆栈溢出:
int count(int i) { return i >= 100000000 ? i : count(i+1); }
回答
据我所知,Perl不会优化尾递归调用,但是我们可以伪造它。
sub f{ my($l,$r) = @_; if( $l >= $r ){ return $l; } else { # return f( $l+1, $r ); @_ = ( $l+1, $r ); goto &f; } }
首次调用时,它将在堆栈上分配空间。然后它将更改其参数,并重新启动子例程,而无需向堆栈中添加任何其他内容。因此,它将假装它从未调用过自身,而是将其更改为迭代过程。
请注意,如果没有," my @_;"或者" local @_;
"将不再起作用。
回答
递归和迭代取决于我们要实现的业务逻辑,尽管在大多数情况下,它可以互换使用。大多数开发人员都希望进行递归,因为它更易于理解。