seegongsik
アルゴリズム

動的計画法 わかりやすく

フィボナッチ数を再帰で求めたことはありますか。fib(5)を求めるにはfib(4)とfib(3)を呼び、fib(4)はさらにfib(3)とfib(2)を呼びます。ところがfib(2)は、あちらでもこちらでも何度も出てきます。同じものを何度も解き直しているのです。一度解いた答えを、どこかに書いておいたらどうでしょう。

01

同じものをまた解く

再帰は大きな問題を小さな問題に分けて解きます。フィボナッチはまさにその例です。
fib(5)はfib(4)とfib(3)を、fib(4)はさらにfib(3)とfib(2)を呼びます。よく見ると、fib(2)があちこちの枝で何度も出てきます。
下の木でノードを一つ押して確かめてみましょう。

fib(5)を再帰で解くと、こんな木が広がります。ノードを一つ押してみましょう。
どのノードでも押してみてください。同じ小さな問題が、木のあちこちに隠れています。

同じ小さな問題が木のあちこちにまた出てきます。(ノードを押す → 同じ値がすべて強調される)

同じfib(2)を、一度ではなく何度も解いています。fib(3)も同じです。
nが大きくなると、この重複は恐ろしいほどふくらみます。
もう解いた答えを何度も解き直すのは、どう見てももったいないことです。

02

答えを書いておく

解決策は意外に単純です。一度解いた答えをメモに書いておくのです。
次に同じものが必要になったら、計算し直さずにメモから取り出して使います。初めてのものだけ計算し、見たことのあるものは取り出して使えばよいのです。
ボタンを押して、呼び出しを一つずつ処理してみましょう。

同じ呼び出しがまた来ます。次の呼び出しを押して処理しましょう。
fib(2)
fib(3)
fib(2)
fib(4)
fib(2)
fib(3)
メモ帳
まだ空です
ボタンを押すと、呼び出しが一つずつ来ます。初めてなら計算、見たことがあればメモから取り出します。

初めてのものは計算して書きとめ、見たことのあるものはメモから取り出して使います。これがメモ化です。

計算するのは最初の一回だけです。残りはすべてメモから取り出すので、同じものを二度解くことがすっかりなくなります。
このように解いた答えを書いておいて使い回す方法を、メモ化と呼びます。
名前はおおげさでも、中身は「書いておくこと」です。

03

下から表を埋める

同じ考えを逆向きにすることもできます。上から割って下りる代わりに、いちばん下の小さな値から表を埋めて上がっていくのです。
fib(0)とfib(1)は最初からわかっています。その二つを足してfib(2)、fib(1)とfib(2)を足してfib(3)。
下のマスがもう書いてあるので、上のマスは足すだけで済みます。

逆に、いちばん下の0と1から表を埋めて上がっていきましょう。
fib(0)0最初からわかる値
fib(1)1最初からわかる値
fib(2)?
fib(3)?
fib(4)?
fib(5)?
fib(6)?
fib(0)=0、fib(1)=1は最初からわかっています。ここから上へ一マスずつ埋めます。

0,1から一マスずつ上へ埋めます。(次のマス = すぐ下の二マスの和 · ボトムアップ)

このやり方では、メモさえ要りません。表そのものがメモだからです。
下から上へ順番に一度なぞるだけで、解き直すことはまったくありません。
メモ化が「必要なときに書いておく」なら、これは「先に全部書いておく」です。考え方の芯は同じです。

04

ぐっと速くなる

では、どれくらい速くなるのでしょうか。書いておくだけで、違いは途方もなく大きくなります。
素朴な再帰は重複まで全部解くので、nが大きくなるほど仕事量が爆発するように増えます。メモ化は各マスをちょうど一回ずつ、nに比例する分だけ解きます。
nを大きくしながら二つを比べると、差がはっきり見えてきます。

nを大きくしながら、二つのやり方の仕事量を比べましょう。
n = 5
素朴な再帰15 回解く
メモ6 回解く
fib(5)では、素朴な再帰は15回、メモは6回解きます。なんと3倍の差です。nが大きくなるほど差が爆発します。

同じnで解く回数。(素朴な再帰 = 指数的に爆発 · メモ = nに比例する線形)

nが少し大きくなるだけで、素朴な再帰の棒は画面を突き抜けそうなほど伸びます。メモの棒はほとんど動きません。
同じ答えを解き直さない、このひとつが遅さと速さを分けるのです。

05

まとめ

動的計画法は難しそうな名前ですが、中身は三つの歩みです。
同じものをまた解いていると気づき、一度解いた答えを書いておき、使い回すので、ぐっと速くなる。
下のカードを一つずつ押して、三つの歩みをまとめましょう。

動的計画法は、たった三つの歩みです。一つずつ押して明らかにしましょう。
1
同じものをまた解く
再帰が同じ小さな問題を何度も解く
2
書いておく
解いた答えをメモに書いて、また取り出す
3
ぐっと速く
指数が線形に、差が爆発する
ボタンを押して、三つの歩みを一つずつ明らかにしましょう。

同じものをまた解く → 書いておく → ぐっと速くなる。かけらが重ならない分割統治では書いておく必要がなかったことも、ここで振り返ります。

前に見た分割統治では、割ったかけらが互いに重ならないので、書いておくものがありませんでした。
そこがまさに分かれ道です。かけらが重なるとき、つまり同じ小さな問題が何度も出てくるとき、はじめて書いておくという一手がすべてを変えるのです。

一言でいうと再帰は、同じ小さな問題を何度も解き直すことがよくあります。フィボナッチの木を見ても、fib(2)があちこちに出てきます。一度解いた答えをメモに書いておいて使い回せば、同じものを二度解くことはありません。これがメモ化です。逆に、いちばん下の小さな値から表を埋めて上がっていくのも同じ考え方です。そうすれば、指数的に遅かったものが線形へと、ぐっと速くなります。分割統治では割ったかけらが重ならないので書いておくものがありませんでしたが、重なるときには書いておくという一手がすべてを変えます。
← アルゴリズム
このページがお役に立ったなら支援する