Recursion 与使用堆栈相比,递归通常被认为是过时的遍历方法吗?

Recursion 与使用堆栈相比,递归通常被认为是过时的遍历方法吗?,recursion,stack,Recursion,Stack,我读过一些地方的文章,人们选择使用堆栈而不是递归。这是因为递归被视为完成工作的过时方法,还是这两种方法在不同的环境中同样适用?否。恰恰相反。递归是表达一整类问题的自然方式。如果没有递归,堆栈是一种模拟方法 例如,请参见。或者考虑一种标准递归函数:计算第n个斐波那契数。 你会记得那是一个系列 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

我读过一些地方的文章,人们选择使用堆栈而不是递归。这是因为递归被视为完成工作的过时方法,还是这两种方法在不同的环境中同样适用?

否。恰恰相反。递归是表达一整类问题的自然方式。如果没有递归,堆栈是一种模拟方法

例如,请参见。或者考虑一种标准递归函数:计算第n个斐波那契数。 你会记得那是一个系列

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中仅用于GRIN)是:

请注意,该程序与原始定义的对应程度如何


问题是,C管理调用堆栈中的所有中间结果。有些语言并没有这样定义(我能想到的唯一一种语言是旧FORTRAN,但我肯定还有其他语言)。如果您是用汇编语言或旧FORTRAN编写的,那么您必须管理自己的堆栈以跟踪这些中间结果

更新了,包括鱼唇修正

使用堆栈是消除错误的标准技术

另见:

尾部递归示例(可使用迭代删除):


迭代通常比递归更快/开销更少。对于递归,我们隐式地使用机器的堆栈作为堆栈——我们“免费”得到它——但我们要支付昂贵的函数调用(以及随之而来的机器堆栈管理)的成本

但是递归函数的编写和读取往往更加直观


通常,可以使用递归编写函数,直到它成为瓶颈,然后用使用显式堆栈的迭代函数替换它。

如果您所在的编程语言/环境中尾部调用会增加堆栈(没有应用尾部调用优化(TCO)),那么最好避免深度递归,首选可能使用堆栈数据结构的迭代解决方案

另一方面,如果您所处的语言/环境支持使用尾部调用进行迭代,或者如果递归深度总是很小,那么递归通常是一个很好的解决方案


(这有点过于宽泛,但总的来说,我决不会称递归为“过时的”。

不,不,我认为现代开发人员应该在几毫秒内强调可读性和易维护性

如果问题是递归的,我完全建议您的解决方案是递归的


此外,您可能会引入一些未经检查的错误,试图强制采用迭代/堆叠解决方案。

我同意您的看法,递归是表示问题的“自然方式”(并相应地对您进行了投票)。然而,我希望看到一些人认识到,它在计算上有点贵,因此也提高了tpdi的投票率。对于某些问题,在某些环境中,计算成本更高。举例来说,这个项目真的很昂贵。另一方面,再多做一点工作,它就可以表示为尾部递归,如下所示:,尾部递归并不比迭代差,而且通常更好,请参见+1-良好的观察。正如Charlie所说,有些问题对于递归来说是很自然的。然而,您应该指出,开发人员需要知道他们正在进行的权衡,但不一定如此:这是一个古老的故事。参见盖伊·斯蒂尔的论文:@Charlie Martin:最安全的说法可能是:这取决于具体情况,因为无法预测编译器/解释器的实现类型。我确信在Lisp中递归速度更快,在Lisp中一切都是递归,如果不是更快,那将是一个严重的问题。和往常一样,这要视情况而定,如果你真的想知道什么更快,就对它进行基准测试。那篇论文并没有真正进行公平的比较。它真正说明的是,一个经过编译器优化的递归算法比一个实现不佳的迭代算法要好。但在这一点上,它只是比较两种迭代算法(编译器的输出是迭代的),当然,实现良好的算法更好。每种递归都可以通过利用堆栈结构进行迭代重写。递归是一种利用调用堆栈来解决问题的方法。然而,尾部递归可以使用GOTOs重写,本质上是将它们转换为迭代循环。这是消除尾部递归的标准方法。你已经抓住了它的要害。您必须根据任务选择正确的工具。但大多数情况下,可读性比在固定点上表达问题更重要。我同意,只要你的工作必须满足与客户协商的要求。如果他们需要您减少程序执行时间,那么您需要检查您的实现选择。
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;
    }
}
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
    }
}