【问题标题】:How does the fibonacci recursive function "work"?斐波那契递归函数如何“工作”?
【发布时间】:2012-02-09 08:04:53
【问题描述】:

当我来到描述函数递归的一章时,我是 Javascript 的新手并且正在阅读它。它使用示例函数来查找斐波那契数列的第 n 个数。代码如下:

function fibonacci(n) {
    if (n < 2){
        return 1;
    }else{
        return fibonacci(n-2) + fibonacci(n-1);
    }
}

console.log(fibonacci(7));
//Returns 21

我无法准确掌握该函数的作用。有人可以解释这里发生了什么吗?我被困在函数调用自身的第 5 行。这里发生了什么?

【问题讨论】:

  • 我让问题更笼统(删除了标题和标签中的“javascript”属性)。
  • 顺便说一句,该代码看起来不正确。应该是if (n &lt; 2) return n;fibonacci(0) 应该返回 0,而不是 1,fibonacci(7) 应该是 13,而不是 21。
  • 不,斐波那契数列从 1 开始,而不是 0。
  • @ThomSmith - 实际上,它可以从 0 开始。0,1,1,2,3,5,8... 我们甚至可以让序列倒退。
  • @woodchips 我认为斐波那契数列实际上应该从 0 开始。

标签: recursion fibonacci


【解决方案1】:

您正在根据自身定义一个函数。一般来说,fibonnaci(n) = fibonnaci(n - 2) + fibonnaci(n - 1)。我们只是在代码中表示这种关系。所以,对于fibonnaci(7),我们可以观察到:

  • fibonacci(7) 等于 fibonacci(6) + fibonacci(5)
  • fibonacci(6) 等于 fibonacci(5) + fibonacci(4)
  • fibonacci(5) 等于 fibonacci(4) + fibonacci(3)
  • fibonacci(4) 等于 fibonacci(3) + fibonacci(2)
  • fibonacci(3) 等于 fibonacci(2) + fibonacci(1)
  • fibonacci(2) 等于 fibonacci(1) + fibonacci(0)
  • fibonacci(1) 等于 1
  • fibonacci(0) 等于 1

我们现在拥有评估 fibonacci(7) 所需的所有部分,这是我们最初的目标。请注意,基本情况 -- return 1 when n &lt; 2 -- 是使这成为可能的原因。这就是停止递归的原因,因此我们可以开始展开堆栈并对我们在每一步返回的值求和的过程。如果没有这一步,我们将继续对越来越小的值调用fibonacci,直到程序最终崩溃。

添加一些说明这一点的日志语句可能会有所帮助:

function fibonacci(n, c) {
    var indent = "";
    for (var i = 0; i < c; i++) {
        indent += " ";
    }
    console.log(indent + "fibonacci(" + n + ")");
    if (n < 2) {
        return 1;
    } else {
        return fibonacci(n - 2, c + 4) + fibonacci(n - 1, c + 4);
    }
}

console.log(fibonacci(7, 0));

输出:

fibonacci(7)
    fibonacci(5)
        fibonacci(3)
            fibonacci(1)
            fibonacci(2)
                fibonacci(0)
                fibonacci(1)
        fibonacci(4)
            fibonacci(2)
                fibonacci(0)
                fibonacci(1)
            fibonacci(3)
                fibonacci(1)
                fibonacci(2)
                    fibonacci(0)
                    fibonacci(1)
    fibonacci(6)
        fibonacci(4)
            fibonacci(2)
                fibonacci(0)
                fibonacci(1)
            fibonacci(3)
                fibonacci(1)
                fibonacci(2)
                    fibonacci(0)
                    fibonacci(1)
        fibonacci(5)
            fibonacci(3)
                fibonacci(1)
                fibonacci(2)
                    fibonacci(0)
                    fibonacci(1)
            fibonacci(4)
                fibonacci(2)
                    fibonacci(0)
                    fibonacci(1)
                fibonacci(3)
                    fibonacci(1)
                    fibonacci(2)
                        fibonacci(0)
                        fibonacci(1)

将同一缩进级别的值相加以产生上一个缩进级别的结果。

【讨论】:

  • 这很适合我。您创建的流程正是我理解它所需要的。出色的工作。
  • 是的,使用console.log 比像我一样尝试手工制作图表要快得多!
  • 如果您正在寻找一种功能性的方式来缓存结果以优化函数调用const fibonacci = (n, cache = {1: 1, 2: 1}) =&gt; cache[n] || (cache[n--] = fibonacci(n--, cache) + fibonacci(n, cache));
  • 在上面的示例中,“fibonacci(0) is equal to 1”是否应该等于 0?
  • 除了这个小错别字之外,这是迄今为止人们可能在万维网上找到的最好的解释..(这是什么..)伟大的工作!
【解决方案2】:

这里有很多很好的答案,但我制作了这张图,有助于更好地解释函数的结果。唯一会返回的值是 1 或 0(您的示例在 n

这意味着每个递归调用最终都会返回 0 或 1。它们最终被“缓存”在堆栈中并“携带”到原始调用中并加在一起。

因此,如果您要为每个“n”值绘制相同的图表,您可以手动找到答案。

此图大致说明了 fib(5) 的每个函数是如何返回的。

这显示了控制流,即函数的执行顺序。记住代码总是左->右和上->下执行。因此,每当调用一个新函数时,它都会暂停,然后进行下一次调用。

以下根据您的原始帖子说明了实际的控制流程。请注意,为简化起见,基本条件为if (n &lt;= 0) {return 0} else if (n &lt;= 2) {return 1;}

1. fib(5) {
    return fib(4) + fib(3);
2.   fib(4) {
      return fib(3) + fib(2);
3.     fib(3) {
        return fib(2) + fib(1);
4.       fib(2) {
A=        return 1;
         };
5.       fib(1) {
B=        return 1;
         };
C=      return 2; // (1 + 1)
       };
6.     fib(2) {
D=      return 1;
       };
E=    return 3; // (2 + 1)
     };
7.   fib(3) {
      return fib(2) + fib(1);
8.     fib(2) {
F=      return 1;
       };
9.     fib(1) {
G=      return 1;
       };
H=    return 2; // (1 + 1)
     };
I=  return 5; // (3 + 2)
   };

【讨论】:

  • 伟大的可视化!尽管我已经知道递归计算的工作原理,但我必须说这可以很好地直观地表示实际总和的计算方式。竖起大拇指!
【解决方案3】:

第 1 步)当fibonacci(7) 被调用时,想象如下(注意我如何将所有 n 更改为 7):

function fibonacci(7) {
    if (7 < 2){
        return 1;
    }else{
        return fibonacci(7-2) + fibonacci(7-1);
    }
}

第 2 步)由于(7 &lt; 2) 显然是错误的,我们转到fibonacci(7-2) + fibonacci(7-1);,它转换为fibonacci(5) + fibonacci(6);,因为fibonacci(5) 首先出现,因此被调用(这次将 n 更改为 5):

function fibonacci(5) {
    if (5 < 2){
        return 1;
    }else{
        return fibonacci(5-2) + fibonacci(5-1);
    }
}

第 3 步)当然fibonacci(6) 也被调用,所以发生的事情是每个人都调用fibonacci 2 个新的fibonacci 被调用。

可视化:

      fibonacci(7)
      ____|_____
     |          |
fibonacci(5)  fibonacci(6)
____|____     ____|_____
|        |    |         |
fib(3)  fib(4) fib(4)   fib(5)

看看它是如何分支的?什么时候停止?当n 小于 2 时,这就是你拥有if (n &lt; 2) 的原因。到那时,分支停止,所有东西都加在一起了。

【讨论】:

  • 非常好。这些图表对于掌握概念非常有帮助。我想这就是我有点模糊的地方。谢谢!
  • 我想知道的是:如果n 小于2,那么return 1 的条件不应该满足吗?为什么不返回`2?
  • @Chrissl: From here: *By definition, the first two numbers in the Fibonacci sequence are either 1 and 1, or 0 and 1,* depending on the chosen starting point of the sequence, and each subsequent number is the sum of the previous two. 它返回 1,因为这是定义序列的方式。
  • @JesseGood 是的,我明白这一点。但是你写了 什么时候停止?当 n 小于 2 时,这就是你有 if (n 为什么它会加在一起?声明说if (n &lt; 2) { return 1;
  • @Chrissl: fibonacci(7-2) + fibonacci(7-1) 你看到两个fibonacci 调用之间的+ 符号了吗?来自fibonacci(7-2)fibonacci(7-1)返回值 相加。 (这只是一个例子)返回值是多少?好吧,这发生在return 语句中,当n 小于2 时,返回1
【解决方案4】:

希望以下内容有所帮助。调用:

fibonacci(3)

将到达第 5 行并执行:

return fibonacci(1) + fibonacci(2);

第一个表达式再次调用该函数并返回 1(从 n &lt; 2 开始)。

第二个再次调用该函数,到达第 5 行并执行:。

return fibonacci(0) + fibonacci(1);

两个表达式都返回 1(因为两个表达式都是 n &lt; 2),所以这个函数调用返回 2。

所以答案是 1 + 2,也就是 3。

【讨论】:

  • 这是有道理的。所以基本上每次调用该函数时,它都会向下钻取到 fibonacci(0) + fibonacci(1),即 1 + 2 - 实际数学正在执行的地方。谢谢!
【解决方案5】:

我认为这两个函数给了我一个更清晰的递归解释(来自blog post):

function fibDriver(n) {
  return n === 0 ? 0 : fib(0, 1, n);
}

function fib(a, b, n) {
  return n === 1 ? b : fib(b, a + b, n-1);
}

【讨论】:

  • 接受的答案可能是递归和堆栈的一个很好的例子,但这个答案在实践中效率更高。
【解决方案6】:
/* * 步斐波那契递归 * 1) 3 次通过。 (在此呼叫期间将 3 打印到屏幕上) * 2) 斐波那契 A 递减 2 并且递归发生将 1 作为参数传递。 (在此呼叫期间将 1 打印到屏幕上) * 3) 斐波那契 A 达到返回 1 的基本情况并“展开”。 (这里没有递归) * 4) 斐波那契 B 被调用,将 n 的前一个值(3 是 A 进行返回调用之前 n 的前一个值)递减为 2。(在此调用期间将 2 打印到屏幕上) * 5) 再次调用斐波那契 A,从 n (2-2=0) 中减去 2,并将 0 作为参数传递。 (在此调用期间将 1 打印到屏幕上,因为它是从 0 转换而来的) * 6) 斐波那契 A 达到基本情况并“展开”(这里没有递归) * 7) 斐波那契 B 被称为从 2 中减去 1(2 是在 A 进行返回调用之前 n 的先前值)并将 1 作为参数传递。 (在此呼叫期间将 1 打印到屏幕上) * 7) 斐波那契 B 现在达到基本情况,返回 1 并“展开”(这里没有递归) * 8) 斐波那契 B 回溯所有先前的函数调用和 n 的值(在我们的例子中为 n=2)并将 [them] 添加到存储在其本地范围中的 n=1 的副本中 * 9) 一旦斐波那契 B 完成“展开”过程,它将计算的值返回给原始调用者(这里没有递归) 笔记* 斐波那契递归的每个实例都创建自己的范围并将返回值存储在 n 的副本中(在我们的例子中为 1)。 当函数“展开”时,它会执行接收 n 值的后续代码。 (所有调用其他函数的函数一旦返回,就会通过先前的调用“展开”) 在我们的斐波那契示例的最后一次调用中,斐波那契 B 在斐波那契 A “展开”时收到 n=2 的值,因为这是它进行返回调用之前的最后一个值。 一旦斐波那契 B 到达基本情况并“展开”,它会通过所有先前的 n 值(在我们的例子中只是 n=2)回溯其步骤,并将 [它们] 添加到其 n=1 的本地副本中。 * 通过数字 3 时的结果是: 3 1 2 1 1 (3) */
var div = document.getElementById('fib');

function fib( n, c ) {
  var indent = "";
  for (var i = 0; i < c; i++) {
    indent += " ";
}
  var v = n===0 ? 1 : n
  var el = document.createElement('div'),
  text = indent + "fibonacci(" + v + ")";
  el.innerHTML = text;
  div.appendChild(el);
  if(n<2){
     return 1;
  } 
  return fib(n-2, c + 4)  + fib(n-1, c + 4);

}

【讨论】:

    【解决方案7】:

    要计算第n个斐波那契数,关系是F(n) = F(n-2) + F(n-1)

    如果我们在代码中实现关系,对于第n个数,我们使用相同的方法计算第(n-2)个和第(n-1)个数。

    每个后续数字都是前两个数字的总和。因此,第七个数字是第六个和第五个数字的总和。更一般地,第n个数字是n - 2n - 1之和,只要n &gt; 2。由于递归函数需要一个停止条件来停止递归,这里 n

    f(7) = F(6) + F(5);
    
    in turn, F(6) = F(5) + F(4)
    
    F(5) = F(4) + F(3)...
    

    一直持续到n&lt;2

    F(1) returns 1
    

    【讨论】:

      【解决方案8】:

      函数正在调用自己。这只是递归函数的定义。在第 5 行中,它通过传递将产生值的参数将执行转移给自己。

      为了确保递归函数不会变成无限循环,必须有某种条件不会调用自身。问题中代码的目标是执行斐波那契数列的计算。

      【讨论】:

      • 我理解那部分,但我不明白它是如何得到结果的(在本例中为 21)。计算它所涉及的数学在哪里?我是否理解通过调用 fibonacci(7) 我有效地调用了函数(在第 5 行)fibonacci(5) + fibonacci(6)?那些函数调用返回什么来得到 21 的结果?
      • @Dan 只需遵循代码流程即可。在纸上完成它(幸运的是,这是一个非常容易用铅笔和纸写出来的功能)。调试它。一步一步通过它。您只需要了解代码的流程。一开始看起来很奇怪,但你会明白的。一步一步来。
      【解决方案9】:

      基于ES6的递归函数斐波那契算法

      const fibonacci = ( n, k = 1, fib2 = 0, fib1 = 1 ) => {
        return k === n ? 
          (() => { return fib1; })() 
          : 
          (() => {
            k++;
            return fibonacci(n, k, fib1, fib1 + fib2);
          })();  
      }
      console.info(' fibonacci ' + 11 + ' = ' + fibonacci(11));
      

      【讨论】:

        【解决方案10】:

        看,fib是

        t(n) = t(n - 1) + n;

        如果 n = 0 那么 1

        让我们看看递归是如何工作的,我只是将t(n) 中的n 替换为n-1 等等。看起来:

        t(n-1) = t(n - 2) + n+1;

        t(n-1) = t(n - 3) + n+1 + n;

        t(n-1) = t(n - 4) + n+1 + n+2 + n;

        .

        .

        .

        t(n) = t(n-k)+ ... + (n-k-3) + (n-k-2)+ (n-k-1)+ n ;

        我们知道如果t(0)=(n-k) 等于1 那么n-k=0 所以n=k 我们将k 替换为n

        t(n) = t(n-n)+ ... + (n-n+3) + (n-n+2)+ (n-n+1)+ n ;

        如果我们省略n-n 那么:

        t(n)= t(0)+ ... + 3+2+1+(n-1)+n;

        所以3+2+1+(n-1)+n 是自然数。它计算为Σ3+2+1+(n-1)+n = n(n+1)/2 =&gt; n²+n/2

        fib 的结果是:O(1 + n²) = O(n²)

        这是理解递归关系的最佳方式

        【讨论】:

          【解决方案11】:

          在 JS/ES6 中使用递归共享一个更简单的 fib 代码。

          function fib(n, first = 1, second = 1) {
              if (n <= 2) return 1;
              [first, second] = [second, first + second];
              return (n - 2 === 1) ? second : fib(n - 1, first, second);
          }
          
          console.log(fib(10));
          

          【讨论】:

            【解决方案12】:

            它会变成这样:

            7 - 2 = 5 --> 斐波那契(5)
            7 - 1 = 6 --> 斐波那契(6)

            就像在实现中给出的那样,左侧总是会减少

            2 和 右手减少1,所以它会以这种方式大小写直到它达到1,一旦它达到1,它将把它加到右手函数1 + fibonacci(n-1);的输出中,这也将永远根据实现停在1's:

            if (n < 2){
              return 1;
            }
            

            最终他们都在内存中拥有1,并开始从左到右添加它们...考虑下面的级联表示,将所有1添加到底部将使其成为@987654330 @。

            if n &gt; 2______________左边总是n - 2 __________&____________右边总是n - 1 ________else n = 1

                                                    7
                               
                                    5                              6
                               3          4                  4              5
                             1    2    2     3            2     3      3        4
                           1     1 1  1 1  1   2         1 1  1  2   1  2    2    3
                                              1 1               1 1    1 1  1 1  1  2
                                                                                   1 1
            
                           1+    1+1+ 1+1 1+  1+1+       1+1+ 1+1+1+ 1+1+1+ 1+1+ 1+1+1 = 21
            

            斐波那契数列的第7位是21,我们可以用数组来测试:

            const fibArray = [1, 1, 2, 3, 5, 8, 13, 21]
            console.log(fibArray[7]) //-> 21
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-12-01
              • 2010-12-03
              • 2020-03-21
              • 2021-11-17
              • 1970-01-01
              • 2011-07-27
              相关资源
              最近更新 更多