C++ 如何准确理解函数递归?

C++ 如何准确理解函数递归?,c++,recursion,C++,Recursion,我目前正在编写一些分治算法,其中函数递归随处可见,但我对它的具体工作原理知之甚少或一无所知,这就是为什么我将其发布在这里,希望您不要介意它太基本 例如,如果我们有以下代码: #include<iostream> using namespace std; void Recursion(int n) { cout << n << endl; if(n > 0) { Recursion(n-1); } cout<<n&l

我目前正在编写一些分治算法,其中函数递归随处可见,但我对它的具体工作原理知之甚少或一无所知,这就是为什么我将其发布在这里,希望您不要介意它太基本

例如,如果我们有以下代码:

#include<iostream>
using namespace std;
void Recursion(int n)
{
  cout << n << endl;
  if(n > 0)
  {
    Recursion(n-1);
  }
  cout<<n<<endl;
}

int main()
{
  Recursion(3);
  return 0;
}
我能理解函数递归调用的概念,但我不理解它是如何工作的。例如,当他们不能再次调用该函数时,他们会做什么?例如,在这里,我可以理解它从3打印到0,但为什么它也从0打印到3?我听说这是因为函数递归存储在一个递归的堆栈中,当它到达“底部”时,它也必须删除

但无论如何,我不知道。那么,有谁能帮我清楚地告诉我这里发生了什么,函数调用的确切流程吗


谢谢你的帮助

函数调用本身与调用另一个函数没有什么不同:在继续之前,它必须等待它调用的函数返回

顺便说一句,递归可能看起来很优雅,但一般来说,它不是最有效的编程方式:例如,它使内联函数变得不可能,因此保证了上下文切换的开销。总有一种更有效的方法可以获得递归函数的相同结果。但是对于某些问题,递归实现更直观,并且不会以任何明显的方式减慢速度。您在注释中给出的示例“合并排序”是一个很好的示例

关于这一点,有几个有趣的讨论:


我的最后一个建议是:当问题不需要这种方法时,不要使用递归。例如,当计算阶乘时。

你是对的,我也发现递归函数很难理解。如果我看到一个递归函数,我会这样做:在脑海中一步一步地运行所有代码。这个建议可能看起来很琐碎,但大多数时候对我有效。让我们看看您的代码: 使用参数3调用递归()函数。它打印n和n>0,这就是它调用递归(2)的原因(注意,我们没有从递归(3)调用返回,我们仍然在其中,现在我们也在递归(2)中。递归(1)和0也是如此。现在n>0条件为假。它打印0。我们从递归(0)返回,打印1,从递归(1)返回,它继续递归(3)


有时,从基本情况(即不递归的情况)开始更容易理解递归。
在您的示例中,当n时,理解递归的关键是调用堆栈的概念。调用堆栈由“帧”组成。堆栈帧包含函数的局部变量和不可见的返回地址。经典的物理类比是板堆栈。当您使函数调用板(堆栈帧)时添加到堆栈顶部。当您从函数返回时,将移除顶板(堆栈框架)。您只能使用顶部的底板(堆栈框架)

递归函数的工作方式与普通函数相同。它们有点棘手,因为在给定时间堆栈上可以有多个局部变量的实例。但是,与其他函数一样,该函数只引用堆栈顶部的堆栈框架

为了说明这是如何工作的,让我们浏览一下您的程序,看看调用堆栈是如何增长和收缩的

让我们从基本情况开始:0。
递归(0);


  • 输入main:堆栈是空的:堆栈底部->| | | | | 0 | | | | | 2 |为您的两个输出语句添加一点区别:您可能会理解所有代码,而不仅仅是递归代码,如果您稍微缩进一点代码会更好。对不起,我在笔记本电脑中缩进得很好,但我不知道如何正确格式化stackoverflow上的代码,这是错误的ems我只能逐行插入。@你可以在编辑器中选择代码段,将其制表4空格,然后复制粘贴到你的问题中。如果你先替换
    cout,但在某些除法克服的问题中,似乎无法避免递归,例如合并排序和矩阵乘法的strassen算法。我能理解这一点吗s方式?如果我到达函数调用的底部,例如这里的递归(0),它将返回递归(1)并在递归(1)中执行其余的代码,因为递归(1)调用RerVersion(0)。通过这个逻辑,它生成结果。你认为这是否正确?在学习CLRS书籍时,不要忘记MIT视频。:)它们也很有价值。毕业后,我仍然记得书中的大部分内容。关于“效率”的评论是一个错误的建议。当然,坎坎还不能考虑微观优化。理解代码更重要,递归问题最自然地导致递归算法。谢谢,伙计,你真的告诉了我我想知道的!
    
    3
    2
    1
    0
    0
    1
    2
    3
    
    Recursion(3)
        Recursion(2)
            Recursion(1)
                Recursion(0)  
                return from Recursion(0)
            return from Recursion(1)
        return from Recursion(2)
    return from Recursion(3)
    
    0
    0
    
    1
    0
    0
    1
    
    2
    1
    0
    0
    1
    2