{ }codelper

学ぶ › JavaScript

さらに先へ · 21

再帰

関数が自分自身を呼びます。基底条件、呼び出しスタックの深さ、ループとメモ化まで。

再帰は関数が自分自身を呼ぶことです。
下の例は各桁の和を再帰で求め(digit_sum(12345) は 5 + digit_sum(1234))、フィボナッチ数をメモしながら求めます。

function digitSum(n) {  if (n < 10) return n; // 바닥: 한 자리면 그대로  return (n % 10) + digitSum(Math.trunc(n / 10));} const memo = new Map();function fib(k) {  if (k <= 1) return k; // 바닥  if (!memo.has(k)) memo.set(k, fib(k - 1) + fib(k - 2)); // 한 번 구한 값은 적어 두기  return memo.get(k);} const [n, k] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);console.log(digitSum(n));console.log(fib(k));
stdin
12345
50
stdout
15
12586269025

ポイント

  • 大きな問題を同じ形の小さな問題に縮めます。
    それ以上縮められないほど小さくなったらすぐに答えを返します。
    これが基底条件(base case)です。
  • 書き方は普通の関数と同じです。
    違うのは、関数の中で自分の名前をもう一度呼ぶことだけです。
  • 呼ぶたびに引数とローカル変数が呼び出しスタックに 1 段積まれ、返ると 1 段降ります。
    そのため深さには限界があります。
  • 1 から n までの和やカウントダウンのように一直線に進む処理は、ループのほうが向いています。
    深さの制限がなく、速いからです。
    再帰が活きるのは枝分かれする処理(フォルダの中のフォルダ、木の形のデータ、すべての場合を試す)です。
  • 一度求めた値を辞書や配列にメモしておけば(メモ化)、同じ計算を繰り返しません。
    この考えを表に広げたものが動的計画法です。

言語ごとの書き方

Pythondef f(n): の中で f(n - 1) — 既定の深さの上限は 1000 段で、超えると RecursionError。
関数の上に @functools.cache を付けるだけでメモ化できます
JavaScriptfunction f(n) { } — 深すぎると RangeError: Maximum call stack size exceeded
TypeScriptJavaScript と同じです
Java返す型を前に書きます(static int f(int n))— 深すぎると StackOverflowError
C#static int F(int n) — StackOverflowException は catch で捕まえられず、プログラムはそのまま終了します
C++int f(int n) — スタックを超えると、たいてい Segmentation fault で落ちます
CC++ と同じです
Craykon名前(引数) { } の中で 返す — 数千段で「関数の呼び出しが深すぎるので止めました」と知らせます。
リストは渡してもコピーされないので、例のメモ用リスト(新リスト(k + 1, 0))をすべての呼び出しが共有します

よくある間違い

基底にたどり着かない

def f(n):
    if n == 0:
        return 0
    return n + f(n)

→ f(n) が自分を延々と呼び続け、最後は深さのエラーで止まります

毎回基底の方向へ縮めます(f(n - 1))。
書く前に「いつ止まるか」と「毎回基底に近づくか」を確かめましょう。

メモなしでフィボナッチを求めた

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

→ 同じ値を何度も計算し直すので、fib(50) は数百億回も呼び出します

求めた値を辞書や配列にメモすれば、100 回ほどの呼び出しで終わります。

言語ごとの違い
再帰には必ず基底条件が必要で、毎回そこに近づかなければなりません。
深さが数千を超えうる問題(入力 n が 10 万など)なら、最初からループで書きましょう。

練習問題