【问题标题】:How does a recursive function iterate?递归函数如何迭代?
【发布时间】:2021-12-09 06:32:11
【问题描述】:

可能是一个非常初级的问题,但令人难以置信。

我有一个斐波那契数列的例子。

fib(int n) {
    if (n <= 1) { //Base Case
        return n;
    }

    return fib(n - 1) + fib(n - 2)
}

所以我主要是在理解函数如何迭代方面存在问题,但它仍然没有 当我逐步打印每个迭代时,这是有道理的。

算法本身有效,但序列如何随着时间的推移变得更小,最终会满足基本情况的条件?

例如,如果我们传入 n=6,第一个数字应该是 9,下一步 n=11,然后它会变得更大。但是当我打印它时,算法从 6-0 开始倒计时,然后我得到 0 到 2 之间的随机数,直到它给我正确的数字。

【问题讨论】:

  • 阶乘可能是递归函数的一个更简单的例子。
  • 请注意,这是一种非常低效的计算斐波那契数列的方法
  • "如果我们传入 n=6,第一个数字应该是 9"。似乎您将函数读取为fib(n - 1 + n - 2),但它是fib(n - 1) + fib(n - 2)
  • "(6-1) + (6-2) = 9" 你错过了fib,一般情况下是f(x) + f(y) != f(x + y)。所以fib(6) == fib(5) + fib(4)。正如预期的那样,数字下降了。
  • 递归函数不是在迭代,而是在递归。

标签: c algorithm recursion fibonacci


【解决方案1】:

当你传入 n=6 时,你的函数会被调用两次,n=5 和 n=4

对于 n=5,它在 n=4 和 n=3 的情况下被调用两次

对于 n=4,它在 n=3 和 n=2 的情况下被调用两次,

...等

最终,所有调用都变为 n=1 或 n=0,其中您的第一个 if 语句停止递归。

【讨论】:

    【解决方案2】:

    您声明您已打印所有值以了解发生了什么。
    我怀疑您只有打印的值,没有说明为什么会出现该值。
    这是一个修改后的代码版本,它告诉您更详细的值故事。
    请注意,该程序非常健忘,一旦计算和使用,就无法记住一个值。如果再次需要相同的值,它会很高兴地再次计算。

    #include <stdio.h>
    
    fib(int n){
        int retValue=0;
      printf("Trying to determine fibonacci at index %d.", n);
      if(n<=1){ //Base Case
        printf(" Easy, it is %d.\n", n);
        return n;
        }
       printf(" No idea. But I could sum up the fibonaccis at index %d and index %d.\n", n-1, n-2);
       retValue= fib(n-1)+fib(n-2);
       printf("I now know the fibonaccis at index %d and at index %d, their sum is %d.\n", n-1, n-2, retValue);
       return retValue;
    }
    
    int main()
    {
        const int TryWith = 6;
        
        printf ("The fibonacci at index %d is %d.\n", TryWith, fib(TryWith));
    
        return 0;
    }
    

    输出是:

    Trying to determine fibonacci at index 6. No idea. But I could sum up the fibonaccis at index 5 and index 4.
    Trying to determine fibonacci at index 5. No idea. But I could sum up the fibonaccis at index 4 and index 3.
    Trying to determine fibonacci at index 4. No idea. But I could sum up the fibonaccis at index 3 and index 2.
    Trying to determine fibonacci at index 3. No idea. But I could sum up the fibonaccis at index 2 and index 1.
    Trying to determine fibonacci at index 2. No idea. But I could sum up the fibonaccis at index 1 and index 0.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    Trying to determine fibonacci at index 0. Easy, it is 0.
    I now know the fibonaccis at index 1 and at index 0, their sum is 1.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    I now know the fibonaccis at index 2 and at index 1, their sum is 2.
    Trying to determine fibonacci at index 2. No idea. But I could sum up the fibonaccis at index 1 and index 0.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    Trying to determine fibonacci at index 0. Easy, it is 0.
    I now know the fibonaccis at index 1 and at index 0, their sum is 1.
    I now know the fibonaccis at index 3 and at index 2, their sum is 3.
    Trying to determine fibonacci at index 3. No idea. But I could sum up the fibonaccis at index 2 and index 1.
    Trying to determine fibonacci at index 2. No idea. But I could sum up the fibonaccis at index 1 and index 0.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    Trying to determine fibonacci at index 0. Easy, it is 0.
    I now know the fibonaccis at index 1 and at index 0, their sum is 1.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    I now know the fibonaccis at index 2 and at index 1, their sum is 2.
    I now know the fibonaccis at index 4 and at index 3, their sum is 5.
    Trying to determine fibonacci at index 4. No idea. But I could sum up the fibonaccis at index 3 and index 2.
    Trying to determine fibonacci at index 3. No idea. But I could sum up the fibonaccis at index 2 and index 1.
    Trying to determine fibonacci at index 2. No idea. But I could sum up the fibonaccis at index 1 and index 0.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    Trying to determine fibonacci at index 0. Easy, it is 0.
    I now know the fibonaccis at index 1 and at index 0, their sum is 1.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    I now know the fibonaccis at index 2 and at index 1, their sum is 2.
    Trying to determine fibonacci at index 2. No idea. But I could sum up the fibonaccis at index 1 and index 0.
    Trying to determine fibonacci at index 1. Easy, it is 1.
    Trying to determine fibonacci at index 0. Easy, it is 0.
    I now know the fibonaccis at index 1 and at index 0, their sum is 1.
    I now know the fibonaccis at index 3 and at index 2, their sum is 3.
    I now know the fibonaccis at index 5 and at index 4, their sum is 8.
    The fibonacci at index 6 is 8.
    

    【讨论】:

      【解决方案3】:

      n 永不改变。它不会变小。相反,每个电话都有自己的n

      让我们看一个更简单但非常相似的例子。

      int fact(int n) {
         if (n <= 1) {
            return 1;
         }
      
        return n * fact(n-1);
      }
      
      • fact(1)1。这很容易。
      • fact(2)2 * fact(1)。从上面我们知道fact(1)1,所以fact(2)2 * 1,也就是2
      • fact(3)3 * fact(2),即3 * 2,即6
      • fact(4)4 * fact(3),即4 * 6,即24
      • fact(5)5 * fact(4),即5 * 24,即120

      斐波那契非常相似。

      • fib(0)1
      • fib(1)1
      • fib(2)fib(1) + fib(0),即1 + 1,即2
      • fib(3)fib(2) + fib(1),即2 + 1,即3
      • fib(4)fib(3) + fib(2),即3 + 2,即5
      • fib(5)fib(4) + fib(3),即5 + 3,即8

      这比上面暗示的要多得多。

        fib(5)
      = fib(4)                                     + fib(3)
      = fib(3)                   + fib(2)          + fib(2)          + fib(1)
      = fib(2)          + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + 1
      = fib(1) + fib(0) + 1      + 1      + 1      + 1      + 1      + 1
      = 1      + 1      + 1      + 1      + 1      + 1      + 1      + 1
      = 8
      

      fib 总共有 15 次调用 fib(5)

      • fib(0): 1 个电话。
      • fib(1):1 个电话。
      • fib(2):3 次通话。
      • fib(3):5 次通话。
      • fib(4):9 次通话。
      • fib(5):15 次通话。

      (这个数列和斐波那契数列非常相似!)

      【讨论】:

      • 添加到我的答案中。 (“这比上面暗示的要多得多。...”)
      【解决方案4】:

      递归函数的基本特征是它调用自身。递归函数并不像您所理解的那样迭代。相反,它会像其中一样发生一次又一次地重复。

      在您的fib 函数中,调用自身的部分在返回中。

      return fib(n - 1) + fib(n - 2)

      在这种情况下,它实际上调用了自己两次。首先,输入变量减少 1 fib(n - 1),其次输入变量减少 2 fib(n - 2),它会一直调用自己,直到 n 小于或等于 1。一旦达到该条件,函数就会启动返回数字。首先,它返回 0 和 1,但之后它开始返回相加的数字。执行步骤显示在@cptFracassa 的答案中。

      注意事项;如果递归函数没有基本/退出案例,那么它将继续不断地调用自身,直到您终止进程或机器发生不良情况。在您的情况下,基本情况是 if 最终停止调用自身并仅返回一个数字。

      【讨论】:

        【解决方案5】:

        了解递归函数如何迭代的一种方法是尝试使用传统的命令式循环来实现递归算法。

        函数调用通常被解释为将一些数据压入调用堆栈的操作,然后调整堆栈的引用框架,然后调用的函数对堆栈顶部的参数进行操作。函数调用的返回将数据从调用堆栈中弹出,函数结果在调用点返回给调用者。

        递归调用只是一个调用自身的函数。要仅使用命令式循环来模拟调用,我们需要一个堆栈。让我们先定义一些堆栈操作,以及堆栈帧的样子。

        #define MAX_STACK_SIZE 256
        
        typedef struct {
            enum { INIT, ONE, DONE } ra;
            int n;
            int result;
        } frame_type;
        
        #define PUSH(...) stack[head++] = ((frame_type){__VA_ARGS__})
        #define POP()     stack[--head]
        #define TOP()     stack[head-1]
        #define EMPTY()   (head == 0)
        

        ra 模拟返回地址,这是被调用函数知道如何返回给调用者的方式。 n 是输入参数,result 是存储任何调用函数结果的地方。

        递归调用将通过循环进行模拟。 CALL() 宏显示保留正确的返回地址,将新的堆栈帧推入堆栈以启动递归调用,并使用 continue 重新启动循环,这将使用新的堆栈帧启动函数。

        #define CALL(N) \
                TOP().ra = top.ra + 1; \
                PUSH(INIT, (N), 0); \
                continue
        

        RET() 宏模拟从递归调用返回。它弹出当前函数调用堆栈帧,然后将计算结果存储到调用者的结果变量中。使用continue 重新启动循环允许调用者在被调用函数返回后在适当的位置继续。

        #define RET(X) \
                POP(); \
                TOP().result += (X); \
                continue
        

        那么现在,让我们看看使用这些宏的fib() 函数会是什么样子。

        stackhead 被定义为给函数一个实际的堆栈以使用宏进行操作。然后,有一些引导代码为stack 提供初始函数调用的一些起始上下文。

        while 实现了将用于模拟递归迭代的传统命令式循环。 top用于保留当前栈帧,switch语句用于跳转到正确的返回地址。

        int fib(int n) {
            frame_type stack[MAX_STACK_SIZE];
            int head = 0;
        
            PUSH(DONE, 0, 0); // Bootstrapping call
            PUSH(INIT, n, 0);
            while (head > 1) {
                frame_type top = TOP();
                switch (top.ra) {
                case INIT: if (top.n < 2) {
                               RET(top.n);
                           } else {
                               CALL(top.n-1);
                case ONE:      CALL(top.n-2);
                           }
                case DONE: if (head == 1) break;
                           RET(top.result);
                }
            }
            POP();
            assert(EMPTY());
            return stack->result;
        }
        

        Try it online!

        【讨论】:

          猜你喜欢
          • 2019-04-03
          • 2016-03-01
          • 2021-12-31
          • 2012-10-17
          • 2019-06-20
          • 1970-01-01
          • 2021-11-03
          • 2020-08-20
          相关资源
          最近更新 更多