TL

再帰

関数が自分自身を呼び出して、大きな問題を「同じ形の小さな問題」に分割して解くテクニック。木構造の探索や分割統治と相性が良い。

中級再帰アルゴリズムコールスタック最終更新: 2026-06-04
TL;DR要点だけ先に
  • 1.再帰とは、関数が自分自身を呼び出し、問題を“同じ形の小さい問題”に分割して解く方法。
  • 2.必ず必要なのは2つ。止まる条件(ベースケース)と、自分を呼ぶ部分(再帰ケース)。どちらが欠けても無限ループになる。
  • 3.1回の呼び出しごとにコールスタックを消費するので、深すぎるとスタックオーバーフローになる。深い反復はループの方が安全なことも多い。

何がうれしい?

ループ(反復)でも同じことはたいてい書けます。それでも再帰が好まれるのは、「全体は部分の繰り返しでできている」構造を、そのままコードに写し取れるからです。

  • 木構造の探索(ファイルのフォルダ、HTML/DOM、JSON)— 「フォルダの中身を処理する」関数が、サブフォルダに対して自分を呼ぶ
  • 分割統治(クイックソート、マージソート、二分探索)— 半分に分けて、それぞれを同じ手順で解く
  • 数学的な定義がそのまま再帰(階乗、フィボナッチ、ユークリッドの互除法)

問題の中に、同じ形の小さい問題が入れ子になっている」と気づいたら、再帰の出番です。

再帰の2つの部品

再帰関数は、必ず次の2つから成り立ちます。どちらか一方でも欠けると、正しく動きません

部品役割階乗での例
ベースケース(基底)それ以上分割せず、答えを直接返して再帰を止める条件n が 0 のとき、1 を返す
再帰ケース自分自身を、より小さい入力で呼び出す部分それ以外は n × factorial(n - 1)
function factorial(n) {
  if (n === 0) return 1;        // ① ベースケース:ここで止まる
  return n * factorial(n - 1);  // ② 再帰ケース:1つ小さい問題を呼ぶ
}
factorial(5); // 120  →  5 × 4 × 3 × 2 × 1

ポイントは、再帰ケースが必ずベースケースに近づいていくこと。上の例では呼ぶたびに n が 1 ずつ減るので、いつか必ず 0(ベースケース)に到達します。

ベースケースを忘れる / 近づかない

ベースケースが無い、あるいは引数がベースケースに近づかない再帰は、止まらずにメモリを食いつぶします(無限再帰)。たとえば factorial(n - 1)factorial(n) と書いてしまうと、同じ値を永遠に呼び続けてクラッシュします。「毎回ちゃんと小さくなっているか?」を必ず確認しましょう。

コールスタックとスタックオーバーフロー

再帰を理解するカギはコールスタックです。関数を呼ぶと、戻り先や途中の変数を記録した「スタックフレーム」が積み上がります。factorial(5) を呼ぶと、factorial(0) が答えを返すまで5個のフレームが積み重なったまま待機します。

factorial(5)  → 5 * factorial(4) を待つ
  factorial(4) → 4 * factorial(3) を待つ
    factorial(3) → 3 * factorial(2) を待つ
      factorial(2) → 2 * factorial(1) を待つ
        factorial(1) → 1 * factorial(0) を待つ
          factorial(0) → 1 を返す(ベースケース)
        ← 1 を返す  ← 2 を返す  ← 6 ... と巻き戻りながら計算

このスタックには容量の上限があります。再帰が深すぎる(=フレームが積み上がりすぎる)と、上限を超えてスタックオーバーフローになります。

深さは入力に比例する

要素数 N のリストを1段ずつ再帰で処理すると、深さも N になります。N が数万〜数十万になりうるなら、再帰の深さも同じだけ膨らみ、スタックオーバーフローの危険があります(多くの環境で数千〜数万段が限界)。**「再帰の深さが、入力サイズに比例して青天井に伸びないか?」**を見積もる癖をつけましょう。

反復(ループ)との比較

同じ階乗をループで書くと、こうなります。スタックを積まないので、深さの心配がありません。

function factorial(n) {
  let result = 1;
  for (let i = 2; i <= n; i++) result *= i;
  return result;
}
観点再帰反復(ループ)
書き方問題を分割する定義をそのまま書ける状態を変数で管理し、明示的に回す
可読性木・分割統治では非常にすっきり深いネスト構造だと逆に複雑になりがち
メモリ呼び出しごとにスタックを消費(深さに比例)原則 O(1) の追加メモリで済むことが多い
リスク深いとスタックオーバーフロー無限ループ(ただしクラッシュはしにくい)
向く問題木構造・分割統治・数学的定義単純な繰り返し・上限の読めない大量データ
迷ったら:構造で選ぶ

データが木や入れ子なら再帰フラットな繰り返しならループが素直です。深さが数十段で収まると分かっているなら、再帰の読みやすさを優先してOK。深さが入力次第で青天井なら、ループや明示的なスタック(配列)への書き換えを検討します。計算量の見積もりについては /programming/big-o/ も参考に。

つまずきポイント:素朴なフィボナッチ

再帰の「うっかり」で有名なのが、フィボナッチ数列の素朴な実装です。定義をそのまま書けて美しいのですが、同じ計算を爆発的に繰り返します

def fib(n):
    if n < 2:
        return n            # ベースケース:fib(0)=0, fib(1)=1
    return fib(n - 1) + fib(n - 2)

fib(5)fib(4)fib(3) を呼び、その fib(4) がまた fib(3) を呼ぶ…と、同じ fib(3) を何度も計算します。計算量はおよそ O(2ⁿ) に膨らみ、fib(50) ですら現実的な時間で終わりません。

再帰=遅い、ではない。重複計算が遅い

遅さの原因は再帰そのものではなく、同じ部分問題を何度も解いていること。一度計算した結果を覚えておくメモ化(キャッシュ)を足すだけで O(n) に改善します。「再帰は遅いから禁止」ではなく、「重複が出ていないか」を見るのが正しい着眼点です。

from functools import lru_cache

@lru_cache(maxsize=None)   # 計算結果を自動でキャッシュ(メモ化)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

末尾再帰という最適化

再帰ケースで、自分自身の呼び出しが「最後の処理」になっている形を末尾再帰(tail recursion)と呼びます。最初の factorialn * factorial(...) と、戻ってきた値に掛け算がまだ残るので末尾再帰ではありません。途中結果を引数で持ち回ると、末尾再帰に書き換えられます。

// 末尾再帰:return が「自分の呼び出しそのもの」で終わる
function factorial(n, acc = 1) {
  if (n === 0) return acc;
  return factorial(n - 1, acc * n); // 呼び出しの後にやることが無い
}

呼び出しの後にやることが無いため、コンパイラ/処理系は今のフレームを使い回して新しいフレームを積まずに済ませられます。これが**末尾呼び出し最適化(TCO)**で、うまく効けば深さが深くてもスタックを消費しません(ループ相当に変換される)。

TCO は当てにしすぎない

末尾再帰に書いても、多くの言語/処理系は実際には最適化してくれません。仕様にある JavaScript でも主要エンジンは未実装、Python は設計方針として TCO を採用していません(Scheme など一部言語は保証)。「末尾再帰だから無限に深くしても安全」と決めつけるのは危険。深さが心配なら、最終的にはループや明示的スタックへ書き換えるのが確実です。

まとめ:再帰を使う前のチェック

  • ベースケースはあるか? そして再帰ケースは毎回それに近づくか?
  • 再帰の深さは入力サイズに比例して青天井にならないか?(なるならループ/明示スタックを検討)
  • 同じ部分問題を重複して解いていないか?(出ているならメモ化)

再帰は「木構造・分割統治・数学的定義」を、人間の考え方そのままに書ける強力な道具です。一方でコールスタックという有限資源の上で動く点を忘れずに。ループと再帰は対立ではなく、問題の形に合うほうを選ぶのが正解です。

プログラミング Article

再帰を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

再帰

比較で見る軸

難易度: intermediate / カテゴリ: プログラミング / タグ数: 3

導入後に効く点

必ず必要なのは2つ。止まる条件(ベースケース)と、自分を呼ぶ部分(再帰ケース)。どちらが欠けても無限ループになる。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
intermediate
カテゴリ
プログラミング
タグ数
3

判断チェックリスト

  • 自社の用途が「再帰 / アルゴリズム」に近いか確認する。
  • 強みである「再帰とは、関数が自分自身を呼び出し、問題を“同じ形の小さい問題”に分割して解く方法。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

再帰アルゴリズムコールスタック再帰アルゴリズムコールスタック
参考: 公式情報