web-dev-qa-db-ja.com

フィボナッチ再帰関数はどのように「機能する」のですか?

私は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行目に行き詰まっています。ここで何が起こっていますか?

58
opes

関数自体を定義しているのです。一般的に、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 いつ n < 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)

同じレベルのインデントの値が合計されて、前のレベルのインデントの結果が生成されます。

87
Wayne Burkett

ここには多くの良い答えがありますが、関数の結果をよりよく説明するのに役立つこの図を作成しました。返される値は1または0のみです(この例では、n <2に対して1を返しますが、代わりにnを返す必要があります)。

これは、各再帰呼び出しが最終的に0または1を返すことを意味することを意味します。これらは最終的にスタックに「キャッシュ」され、元の呼び出しに「繰り越されて」加算されます。

したがって、「n」の各値に対してこの同じ図を描くと、手動で答えを見つけることができます。

この図は、fib(5)に対してすべての関数がどのように返されるかを大まかに示しています。

![Fibonacci Javascript Tree Diagram

これは、制御フロー、つまり関数の実行順序を示しています。コードは常に左->右および上->下で実行されることに注意してください。そのため、新しい関数が呼び出されるたびに一時停止され、次の呼び出しが発生します。

以下は、元の投稿に基づいた実際の制御フローを示しています。簡単にするために、基本条件はif (n <= 0) {return 0} else if (n <= 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)
   };
28
Jeff Callahan

ステップ1)fibonacci(7)が呼び出されるとき、次のことを想像してください(すべてのnを7に変更したことに注意してください):

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

ステップ2)(7 < 2)は明らかにfalseであるため、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 new fibonacciの呼び出しがすべて呼び出されます。

可視化:

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

分岐方法をご覧ください。いつ停止しますか? nが2未満になると、それがif (n < 2)を持っている理由です。その時点で分岐が停止し、すべてが一緒に追加されます。

20
Jesse Good

うまくいけば、次のことが役立ちます。呼び出し:

fibonacci(3)

5行目に到達し、次のことを行います。

return fibonacci(1) + fibonacci(2);

最初の式は関数を再度呼び出し、1を返します(n < 2)。

2番目の関数は関数を再度呼び出し、5行目に到達して次を実行します。

return fibonacci(0) + fibonacci(1);

両方の式は1を返します(n < 2両方)ので、この関数呼び出しは2を返します。

したがって、答えは1 + 2、つまり3です。

5
RobG

私はこれら2つの関数が再帰のはるかに明確な説明を私に与えたと思う(これから ブログ投稿 ):

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);
}
3
Sl4rtib4rtf4st
 
 /*
*ステップフィボナッチ再帰
 * 1)3が渡されます。 (この呼び出し中に3が画面に出力されます)
 * 2)フィボナッチAは2ずつ減少し、1をパラメーターとして渡して再帰が発生します。 (この呼び出し中に画面に1が出力されます)
 * 3)フィボナッチAは1を返すベースケースにヒットし、「巻き戻し」ます。 (ここでは再帰なし)
 * 4)フィボナッチBが呼び出され、nの前の値をデクリメントします(3はAが戻る呼び出しを行う前のnの前の値でした)2に(2は画面に出力されますthis call)
 * 5)フィボナッチAが再度呼び出され、nから2を減算し(2-2 = 0)、パラメーターとして0を渡します。 (0から変換されるため、この呼び出し中に1が画面に出力されます)
 * 6)フィボナッチAがベースケースにヒットし、「巻き戻し」(ここでは再帰なし)
 * 7)フィボナッチBは2から1を引くと呼ばれ(2は、Aが戻る呼び出しを行う前のnの以前の値でした)、1をパラメーターとして渡します。 (この呼び出し中に画面に1が出力されます)
 * 7)フィボナッチBがベースケースにヒットし、1を返して「巻き戻し」(ここでは再帰なし)
 * 8)フィボナッチBがリトレースします以前のすべての関数呼び出しとnの値(この例ではn = 2)を戻り、ローカルスコープに保存されたn = 1のコピーに[them]を追加します
 * 9)フィボナッチBが「巻き戻し」プロセスでは、計算された値を元の呼び出し元に返します(ここでは再帰なし)
 
注* 
フィボナッチ再帰の各インスタンスは独自のスコープを作成し、返された値をnのコピー(この場合は1)。 
関数が「アンワインド」すると、その時点でnの値を受け取る後続のコードを実行します。 (他の関数を呼び出すすべての関数は、戻ると以前の呼び出しを「巻き戻す」)
 
フィボナッチの例の最後の呼び出しでは、フィボナッチBはn = 2の値をフィボナッチAとして受け取ります。 
フィボナッチBがベースケースに到達して「巻き戻される」と、nの以前のすべての値(この場合はn = 2)そして、n = 1。
 
 *のローカルコピーに[それら]を追加しました。数字3を渡すと、結果は次のようになります。 ____。] 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);

}

2
Bill Pope

N番目のフィボナッチ数を計算するには、関係はF(n) = F(n-2) + F(n-1)。

コードにリレーションを実装する場合、n番目の数値について、同じ方法を使用して(n-2)番目と(n-1)番目の数値を計算します。

後続の各数値は、前の2つの数値の合計です。したがって、7番目の数値は6番目と5番目の数値の合計です。より一般的には、n> 2である限り、n番目の数値はn-2とn-1の合計です。再帰関数は、再帰を停止するための停止条件が必要なので、ここでn <2が条件です。

f(7)= F(6) + F(5);

順番に、F(6) = F(5) + F(4)

F(5)= F(4) + F(3)... n <2まで続く

F(1)は1を返します

2
Sunil

関数はそれ自体を呼び出しています。これは単に再帰関数の定義です。 5行目では、値をもたらすパラメーターを渡すことにより、実行をそれ自体に転送しています。

再帰関数が無限ループにならないようにするには、does n't自体を呼び出すような条件が必要です。問題のコードの目標は、フィボナッチ数列の計算を実行することです。

1
user596075

再帰を使用して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));
0
Sumer

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));
0
Roman

見て、フィブは

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_はknに置き換えます。

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 => n²+n/2として計算されます

fibの結果は次のとおりです:O(1 + n²) = O(n²)

これが再帰関係を理解する最良の方法