フィボナッチ数(再帰)

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

フィボナッチ数(再帰)

このレッスンで分かること

  • フィボナッチは 2 つの基底ケース を持つ
  • 素朴な再帰版の計算量は O(2^n) (実際は黄金比の n 乗) です
  • フィボナッチ数 は「直前 2 つの数を足して次の数を作る」数列です

フィボナッチ数 とは

fib(n) = fib(n - 1) + fib(n - 2) を再帰で実装する。2 つの基底ケースが必要なパターンを学ぶ。

フィボナッチ数 は「直前 2 つの数を足して次の数を作る」数列です。具体的には fib(0) = 0, fib(1) = 1, fib(2) = 1, fib(3) = 2, fib(4) = 3, fib(5) = 5, fib(6) = 8 … と続きます。これは植物の枝分かれや、黄金比など、自然界にも顔を出す不思議な数列です。

フィボナッチは 2 つの基底ケース を持つ。階乗より一段だけ複雑な、典型的な再帰パターンだ。

本レッスンでは、この数列を 再帰 で計算する関数を書きます。前回の factorial と違うのは、基底ケースが 2 つあり、再帰呼び出しも 2 回あることです。

数学的な定義

フィボナッチは数学では次のように定義されます。

  • fib(0) = 0
  • fib(1) = 1
  • fib(n) = fib(n - 1) + fib(n - 2) (n >= 2)

この定義をそのままコードに落とせば、それが 再帰実装 になります。Python ならこうです。

Python

def fib(n): if n <= 1: return n return fib(n - 1) + fib(n - 2)

if n <= 1: return n の 1 行で fib(0) = 0fib(1) = 1 の両方をまとめて表現できます。これは小さなテクニックですが、覚えておくとコードが短くなります。

再帰木 (recursion tree)

fib(5) を計算すると、内部で何回再帰が呼ばれるでしょうか。Mermaid で 再帰木 を描いてみます。

diagram (will load when visible)

図のポイント (テキスト併記)

  • fib(3) が 2 回、fib(2) が 3 回呼ばれていることが分かります

fib(3) が 2 回、fib(2) が 3 回呼ばれていることが分かります。再帰 が枝分かれするたびに、同じ計算を何度も繰り返す 重複計算 が発生する、というのが素朴な再帰版フィボナッチの弱点です。

計算量はどのくらい?

素朴な再帰版の計算量は O(2^n) (実際は黄金比の n 乗) です。n = 40 あたりでもう数秒かかるレベルになります。

同じ計算を何度もやり直す 重複部分問題 を持つ問題は、メモ化動的計画法 で高速化できる。

本レッスンでは 素朴な再帰 のみを扱います。メモ化 の話は第 6 章 (動的計画法) で詳しく扱うので、まずは「再帰で書ける」状態を体に染み込ませてください。

JavaScript / Java での書き方

JavaScript

function fib(n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }

Java

public class Solution { public static int fib(int n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } }

どの言語でも、コードはほぼ写経できます。再帰 の表現はそれだけ言語を超えて共通している、ということです。

よくある間違い

  • if n <= 1: return 1 と書くと fib(0) = 1 になってしまう。正しくは return n
  • return fib(n - 1) だけにしてしまい、もう片方の + fib(n - 2) を忘れる。
  • n = 50 のような大きな入力でいきなり試すと、何分も待たされる。再帰 の遅さを実感する意味ではアリだが、テストは n <= 20 程度に。

やってみよう

下の coding 課題で、fib(n) を再帰で実装してください。テストでは 0 から 15 までの値で正しい結果が返るかを確認します。Mermaid の 再帰木 を頭の中で描けるようになるのが目標です。

数列の世界では、フィボナッチ リュカ数 トリボナッチ など、漸化式 で定義される数列が多数ある。再帰 はこれらを最も自然に表現できる道具だ。

Go 版での書き方

Go

func fib(n int) int { if n <= 1 { return n } return fib(n-1) + fib(n-2) }

Go でも基本は同じ。シンプルな関数定義に基底ケースと再帰呼び出しを書くだけです。Go:= (短い変数宣言) は今回は不要で、ifreturn だけで完結します。

フィボナッチの歴史的背景

フィボナッチ は 13 世紀イタリアの数学者 レオナルド・フィボナッチ が、ウサギの繁殖モデルで紹介した数列です。1 つがいのウサギが、毎月新しいつがいを産む、というモデル化が f(n) = f(n - 1) + f(n - 2) の形になります。螺旋 黄金比 植物の葉序 など、自然界の多くの現象とつながりが見つかっており、数学と自然 の橋渡しとして語り継がれてきました。プログラミングの世界でも、再帰 の入門題材として真っ先に登場する 古典中の古典 です。

よくある間違いの追加

素朴な再帰は n が大きいと スタックオーバーフロー になることがあります。n = 1000 程度から危険な言語もあるので、本番でフィボナッチを計算するときは メモ化反復版 (ループ) を使うのが普通です。本レッスンでは n <= 20 までしかテストしないので心配いりません。

よくある質問

Q. 再帰とループはどっちを使うべき?

A. ツリー構造や分割統治(merge sort, BST 走査)は再帰が自然で読みやすくなります。シンプルな反復処理はループが速くスタックも消費しません。再帰深さが配列長に比例する場合、スタックオーバーフローのリスクがあるため気をつけてください。

Q. 再帰のベースケースを忘れるとどうなる?

A. 無限再帰になりスタックオーバーフローします。最初の 1 行目に if (base) return; を書く習慣を付けると安全です。Python は sys.setrecursionlimit で上限を変えられますが、根本的にループに書き換える方が安定します。

Q. 再帰を高速化する方法は?

A. 同じ引数で再計算しているなら memoize(メモ化)で結果をキャッシュします。Python は functools.lru_cache、Java/JS は HashMap で実装します。フィボナッチを memo 付きにすると指数 O(2ⁿ) から線形 O(n) に劇的に高速化します。

次のレッスン

次は 累乗(再帰) で、pow(base, exp) を分割統治で高速に計算する方法を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

  1. フィボナッチ の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. フィボナッチ とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. 関数 fib(n) を実装し、整数値を返す
  2. for / while を使わず、必ず再帰で実装する
  3. n <= 1 の場合は n をそのまま返す (2 つの基底ケースをまとめる)

入出力例

test-cases.txt

fib(0)0 fib(1)1 fib(2)1 fib(6)8 fib(10)55 fib(15)610

ヒント

main.py
main.py
学習モード

メモ

フィボナッチ数(再帰)

⌘S で保存