如何准确理解函数递归?

时间:2013-07-27 22:34:13

标签: c++ recursion

我目前正在编写一些分治算法,其中函数递归在任何地方使用,但我有一个非常模糊的想法或不知道它究竟是如何工作的,这就是为什么我在这里发布它并希望你不介意它也是基本

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

#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;
}

我测试了Recursion(3),终端中的打印输出是:

3
2
1
0
0
1
2
3

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

但无论如何,我不知道。那么,任何人都可以帮助我,并清楚地告诉我这里发生了什么以及函数调用的确切流程吗?

感谢您的帮助!

4 个答案:

答案 0 :(得分:5)

理解递归的关键是调用堆栈的概念。调用堆栈由“框架”组成。堆栈帧包含函数的局部变量和不可见的返回地址。经典的物理比喻是一堆板块。当您进行函数调用时,将板(堆栈框架)添加到堆栈顶部。从功能返回时,顶板(堆叠框架)被移除。您只能使用顶部的板(堆叠框架)。

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

为了说明这是如何工作的,让我们逐步介绍一下程序,显示调用堆栈如何增长和缩小。

让我们从基本案例开始:0。Recursion(0);

  1. 输入main:堆栈为空:堆栈底部 - &gt; ||&lt; -Top of stack
  2. Recursion(0);输入堆栈增长的递归:堆栈底部 - &gt; | 0 |&lt; -Top of stack
  3. cout << n << endl; n的值为0,因此输出为“0”
  4. if (n > 0)。 0不大于0因此不会调用Recursion(-1)。
  5. cout << n << endl; n的值为0,因此输出为“0”
  6. 返回main()堆栈再次为空:堆栈底部 - &gt; ||&lt; -Top of stack
  7. 输出为

    0
    0
    

    很简单,没有发生递归。让我们迈出下一步。 Recursion(1);

    1. 输入main:堆栈底部 - &gt; ||&lt; -The top of stack
    2. Recursion(1);输入递归:堆栈底部 - &gt; | 1 |&lt; -Top of stack
    3. cout << n << endl; n的值为1,因此输出为“1”
    4. if (n > 0)。 1大于0,因此调用Recursion(0);
    5. 输入递归:堆栈底部 - &gt; | 1,0 |&lt; -Top of stack
    6. cout << n << endl;此堆栈帧中的n值为0,因此输出为“0”
    7. if (n > 0)。 0不大于0,因此该函数不会递归。
    8. cout << n << endl; n的值为0,因此输出为“0”
    9. 返回第一次调用Recursion。堆栈底部 - &gt; | 1 |&lt; -Top of stack
    10. cout << n << endl; n的值为1,因此输出为“1”
    11. 输出

      1
      0
      0
      1
      

      让我们最后一次执行n == 2

      1. 输入main:Bottom-&gt; ||&lt; -Top
      2. Recursion(2);输入递归:底部 - &gt; | 2 |&lt; -Top
      3. cout << n << endl;“2”
      4. if (n > 0)。 2大于0,因此调用Recursion(1);
      5. 输入递归:底部 - &gt; | 2,1 |&lt; -Top
      6. cout << n << endl;“1”
      7. if (n > 0)。 1大于0,因此调用Recursion(0);
      8. 输入递归:底部 - &gt; | 2,1,0 |&lt; -Top
      9. cout << n << endl;“0”
      10. if (n > 0)。 0不大于0因此该函数不会再次递归。
      11. cout << n << endl;“0”
      12. 返回。自下而上&GT; | 2,1 |&LT;机顶
      13. cout << n << endl;“1”
      14. 返回。自下而上&GT; | 2 |&LT;机顶
      15. cout << n << endl;“2”
      16. 返回main()。自下而上&GT; ||&LT;机顶
      17. 输出

        2
        1
        0
        0
        1
        2
        

答案 1 :(得分:4)

你是对的,我也发现递归函数难以理解。这就是我所做的,如果我看到一个递归函数:在你的脑海里一步一步地运行所有代码。这个建议可能看似微不足道,但大部分时间它对我有用。我们来看看你的代码: 你用参数3调用Recursion()函数。它打印n和n> 0,这就是它调用Recursion(2)的原因(注意我们没有从Recursion(3)调用返回我们仍在其中,现在我们也是在递归(2)。同样是递归(1)和0.现在n&gt; 0条件是假。它打印0.我们从递归返回(0)我们打印1并从递归(1)返回它去关于递归(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)

答案 2 :(得分:3)

调用自身的函数与调用另一个函数的函数没有区别:在继续之前,它必须等待它调用的函数返回。

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

关于此的几个有趣的讨论:
Way to go from recursion to iteration
Can every recursion be converted into iteration?

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

答案 3 :(得分:2)

有时通过从基本情况开始,即不会递归的情况,更容易理解递归。 在您的示例中,当n <= 0时,无需更多呼叫即可解析呼叫。让我们从那开始。

如果调用Recursion(0),则预期结果是两次打印零,一次在if之前,一次在if之后。 if中的代码不会执行。

现在,Recursion(1)首先打印第一个值,然后调用Recursion(0),然后像之前一样打印0,然后执行返回到Recursion(1),再次打印1,执行递归(0)。这就是为什么你看到1 0 0 1。 这同样适用于递归(2),它将递归(1)的结果包装在两个左右。