与使用堆栈相比,递归通常被认为是一种过时的遍历方法吗?
Posted
技术标签:
【中文标题】与使用堆栈相比,递归通常被认为是一种过时的遍历方法吗?【英文标题】:Is recursion generally considered to be an outdated method of traversing compared to using a stack? 【发布时间】:2010-10-22 04:58:33 【问题描述】:我一直在阅读一些人们选择使用堆栈而不是递归的地方。这是因为递归被视为完成工作的一种过时方式,还是两种方法都同样适用于不同的上下文?
【问题讨论】:
【参考方案1】:没有。恰好相反。递归是表达一整类问题的自然方式。如果没有递归,堆栈是一种模拟方式。
例如,请参阅此question。或者考虑一种标准递归函数:计算第 n 个斐波那契数。
你会记得Fibonacci numbers是系列
0,1,1,2,3,5,8,13, ...
定义为 Fn = Fn-1+Fn-2。
这可以写成递归定义
基本情况: F(0) = 0 F(1) = 1 递归步骤: F(n) = F(n-1)+F(n-2)
所以,你有 F(0) = 0,F(1)= 1,F(2)=F(0)+F(1)=1,等等。
计算这个的简单程序(在 C 中只是为了笑)是:
int fib(int n)
/* we'll ignore possible negative arguments, see Wikipedia */
switch(n)
case 0: return 0; break;
case 1: return 1; break;
default: return fib(n-1)+fib(n-2); break;
注意该程序与原始定义的对应程度有多接近?
问题是,C 管理调用堆栈中的所有中间结果。有些语言没有被定义为这样做(我能想到的唯一一种是旧的 FORTRAN,但我相信还有其他的)。如果您使用汇编程序或旧 FORTRAN 编写,那么您必须管理自己的堆栈以跟踪这些中间结果。
【讨论】:
我同意你关于递归是表达问题的“自然方式”(并相应地支持你)的观点。但是,我希望看到一些认可,即它的计算成本更高一些,因此也受到了 tpdi 的支持。 除非那不是真的。对于某些问题和某些环境,它的计算成本更高。例如,这个程序非常昂贵。另一方面,如果做更多的工作,它可以表示为尾递归,如下所示:triton.towson.edu/~akayabas/COSC455_Spring2000/…,尾递归并不比迭代差,而且通常更好,参见portal.acm.org/citation.cfm?id=800055.802039【参考方案2】:迭代通常比递归更快/开销更少。通过递归,我们隐式地使用机器的堆栈作为我们的堆栈——我们“免费”获得它——但我们付出了昂贵的函数调用(以及随之而来的机器堆栈管理)的成本。
但递归函数通常更易于书写和阅读。
通常,可以使用递归来编写函数,直到它成为瓶颈,然后将其替换为使用显式堆栈的迭代函数。
【讨论】:
+1 - 良好的观察力。正如查理所说,有些问题对于递归来说是非常自然的。但是,您最好指出开发人员需要了解他们正在做出的权衡。 除了不一定如此:这是一个老妇人的故事。请参阅 Guy Steele 的论文:portal.acm.org/citation.cfm?id=800055.802039 @Charlie Martin:最安全的说法可能是:这取决于,因为无法预测编译器/解释器的实现类型。我确信 Lisp 中的递归更快,Lisp 中的一切都是递归,如果不是更快,那将是一个严重的问题。与往常一样,这取决于您,如果您真的想知道什么更快,请对其进行基准测试。 那篇论文并没有真正进行公平的比较。它真正说的是,经过编译器优化的递归算法比实现不佳的迭代算法要好。但在这一点上,它只是比较两种迭代算法(编译器的输出是迭代的),当然实现良好的算法更好。【参考方案3】:更新以包含鱼唇修正。
使用堆栈是消除recursion 的标准技术
另见:What is tail-recursion?
尾递归的一个例子(可以使用迭代删除):
public class TailTest
public static void Main()
TailTest f = new TailTest();
f.DoTail(0);
public void DoTail(int n)
int v = n + 1;
System.Console.WriteLine(v);
DoTail(v); // Tail-Recursive call
【讨论】:
每一种递归都可以通过利用栈结构迭代地重写。递归是一种利用调用栈来解决问题的方法。但是,尾递归可以使用 GOTO 重写,本质上是将它们转换为迭代循环。这是消除尾递归的标准方法。【参考方案4】:如果您所处的编程语言/环境中尾调用会增加堆栈(未应用尾调用优化 (TCO)),那么最好避免深度递归,并且首选可能使用堆栈数据结构的迭代解决方案.
另一方面,如果您处于支持尾调用迭代的语言/环境中,或者如果递归深度总是很小,那么递归通常是一个很好/优雅的解决方案。
(这有点过于宽泛,但总的来说,我绝不会称递归“过时”。)
【讨论】:
【参考方案5】:不不,我认为现代开发人员应该强调几毫秒内的可读性和易于维护。
如果问题是递归的,我完全推荐你的递归解决方案。
此外,您最终可能会引入一些意想不到的错误,试图强制执行迭代/堆叠解决方案。
【讨论】:
你把钉子钉在了头上。您必须根据任务选择正确的工具。但大多数情况下,可读性比在固定点中表达问题更重要。 我同意,只要清楚您的工作必须满足与客户协商的要求即可。如果他们需要您减少程序执行时间,那么您需要检查您的实施选择。以上是关于与使用堆栈相比,递归通常被认为是一种过时的遍历方法吗?的主要内容,如果未能解决你的问题,请参考以下文章