再帰 ・ O(n)

再帰 (階乗)

関数が自分を呼んで積み重なり、終わりの条件で折り返して値を返します。

1 歩ずつ動かす

  1. factorial(4)いま動いている
戻ってきた値
まだ無い

これまでに戻った値 (返した順)

まだどの呼び出しも返していません。

  • いま動いている呼び出し
  • 戻りを待っている呼び出し
  • 戻ってきた値
  • 空いている枠

図の箱は、まだ答えを返していない呼び出しです。下から順に呼び、上から順に返します。

1 歩目 / 全 8 歩呼んだ回数 1 回積まれている呼び出し 1 個

factorial(4) を呼びます。積まれた呼び出しは 1 個です。

いま動いている疑似コードの行

  1. 1factorial(n)
  2. 2もし n == 1 なら
  3. 31 を返す (終わりの条件。ここで折り返す)
  4. 4r = factorial(n - 1) を呼んで待つ
  5. 5n × r を返す

見本を変える

予想してみる

いまは 1 歩目です。
次に呼ばれるのは、どれでしょう。

今回わかること

  • 関数が自分を呼ぶたびに、呼び出しが上へ積み重なる流れ
  • 終わりの条件で折り返し、値が逆の順で戻ってくる理由

前提

  • 関数の呼び出しと return
  • スタック (最後に積んだ物から出る)

どんな考え方か

再帰は、関数の中で同じ関数を呼び、問題を 1 つ小さくして解く考え方です。階乗なら、n の階乗は n × (n - 1 の階乗) と書けます。

小さくし続けると、いつか答えがすぐ分かる形に行き着きます。これを終わりの条件と呼びます。終わりの条件が無いと呼び出しが止まらず、積み重なりがあふれてエラーになります。

手順と具体例

  1. 1n が 1 なら、もう呼ばずに 1 を返す (終わりの条件)
  2. 2そうでなければ、factorial(n - 1) を呼び、答えが戻るまで待つ
  3. 3戻ってきた値に n をかけて返す
  4. 4返した呼び出しは積み重なりから外れ、1 つ下の呼び出しが続きを動かす
factorial(4) を呼んだとき (上から呼ばれる順、返すのは下から)
呼び出し中でしていること返す値
factorial(4)4 × factorial(3)24
factorial(3)3 × factorial(2)6
factorial(2)2 × factorial(1)2
factorial(1)終わりの条件。呼ばずに 1 を返す1

呼び出しの回数と計算量

数えるもの回数と量計算量
呼び出しの回数n 回O(n)
かけ算の回数n - 1 回O(n)
いちばん高く積まれる数n 個 (その分のメモリを使う)O(n)

n が 6 なら 6 回呼び、いちばん深いところで 6 個積まれます。Python は既定で 1000 段ほど積むと RecursionError で止まるので、とても深い再帰は for 文に書き直します。

使いどころ

向いているとき

  • 問題を同じ形の小さい問題に分けられるとき (階乗、フォルダの中のフォルダ)
  • 木やグラフを深くたどるとき (深さ優先探索)
  • 分けて解いてから合わせるとき (マージソート)

向いていないとき

  • 何万段も深くなるとき (積み重なりがあふれる。for 文で書く)
  • 同じ呼び出しを何度もくり返すとき (結果を覚えておく工夫が要る)

コード

疑似コード

  1. 1factorial(n)
  2. 2もし n == 1 なら
  3. 31 を返す (終わりの条件。ここで折り返す)
  4. 4r = factorial(n - 1) を呼んで待つ
  5. 5n × r を返す

実際のコード

python実行可能
出力

▶ 実行ボタンを押すと結果がここに表示されます

Python と JavaScript は、書き換えて「実行」を押すと、その場で結果が出ます。Java は読むだけの欄です。

よくある疑問

終わりの条件を書き忘れると、どうなりますか?
factorial(0)、factorial(-1) と呼び出しが止まらずに積み重なり、やがてあふれます。Python では RecursionError、Java では StackOverflowError になります。
スタックと何の関係がありますか?
まだ返していない呼び出しは、スタックと同じく上に積まれ、最後に呼んだ物から返ります。この積み重なりを、呼び出しのスタックと呼びます。
for 文で書くのと、どちらがよいですか?
階乗なら for 文のほうが速く、深さの心配もありません。再帰は、木のたどり方のように、分けて考えると読みやすくなる問題で使います。

関連