動的計画法
最後に、経路探索や自然言語処理などにも使われる動的計画法について学習します。
フィボナッチ数列
動的計画法の説明に必ずといってもいいほどよく使われるフィボナッチ数列を例に説明していきます。
フィボナッチ数列 ( )は、次のように定義されるのでした。
再帰を使ったフィボナッチ数列
再帰を使ったプログラムは次のように作れました。
しかし、これでは計算量が なので n
が小さければこれで問題ありませんが、n
が大きくなると時間がかかりすぎます。このプログラムで n = 40
としたら、実行するのに 1 分もかかりました。
原因は、次の図をみるとよくわかります。同じ数字を何度も計算してしまっています。たとえば、fib(2)
は 3 回も計算しています。n
がさらに大きくなると大変です。
DP を使ったフィボナッチ数列
これを解決するのが、動的計画法(Dynamic Programming)です。DP とよく言われます。
DP とは大きな問題を解くときに出てくる小さな問題の解を表に記録していき、その解を利用して次の計算を進めていくアルゴリズムのことです。
DP には大きく分けて、二種類あります。トップダウン方式とボトムアップ方式です。
トップダウン方式
計算結果をメモ化して無駄な計算を省くのがトップダウン方式です。メモ化再帰とも呼ばれます。
配列に計算結果をメモしておいて、すでに計算してあったらその値を再利用します。
メモ化してフィボナッチ数列を解くプログラムは次のようになります。これなら、計算量は なので、n = 40
どころか n = 100
でも余裕で計算できます。
ボトムアップ方式
ボトムアップ方式は、f(2)
を求めてから f(3)
を求めて f(4)
を求める…というように下から順番に求めていこうというものです。
プログラムは次のようになります。こちらも、計算量は です。
部分和問題
次に、部分和問題(SSP)を解いてみましょう。
部分和問題(ぶぶんわもんだい)は、計算複雑性理論・暗号理論における問題で、与えられた 個の整数 から部分集合をうまく選んで、その集合内の数の和が与えられた数 に等しくなるようにできるかどうかを判定する問題である。NP 完全であることが知られている。 -- フリー百科事典『ウィキペディア(Wikipedia)』
例としては、3、4、6 から部分集合をうまく選んで 10 を作れるかという問題であれば、4 と 6 を足せば、10 であるのでできるという答えになります。
全探索を使う
この問題は、全探索すれば解くことは可能です。 を含めるか含めないかの 2 通りずつがあるので、計算量は、 です。説明が長くなるので、全探索のプログラムは飛ばします。
動的計画法を使う
動的計画法を使って、部分和問題を解いていきましょう。
DP テーブルを作る
3、4、6 から部分集合をうまく選んで 10 を作れるかという問題を考えます。
動的計画法は、表を作って考えるので次のように表を用意します。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
表の 行 列は、 の中からいくつかを使って をつくることができるかの真偽値とします。(真は 1、偽は 0 とします)
まず 0 行目を考えます。
行 列は、 の中からいくつかを使って をつくることができるかの真偽値となります。 0 しか作れません。
よって、次のようになります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 行目を考えます。
行 列は、 の中からいくつかを使って をつくることができるかの真偽値となります。 0 と 3 なら作れます。
よって、次のようになります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
次に、2 行目を考えます。
行 列は、 の中からいくつかを使って をつくることができるかの真偽値となります。 0 と 3、4、7 が作れます。
よって、次のようになります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | |
次に、3 行目を考えます。
行 列は、 の中からいくつかを使って をつくることができるかの真偽値となります。 0 と 3、4、6、7、9、10 が作れます。
よって、次のようになります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | 0 | |
1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 |
漸化式を作る
表を元に漸化式を作っていきます。
行目を考える時、 を入れるか入れないかの二択になります。
- を入れないときは、何も変わらないので一個上のセルの値の真偽値そのままです。つまり、 行 列の真偽値は、 行 列の真偽値となります。
- を入れるときは、 を使って、 を作ることができていれば、それに を加えることで を作れそうです。 例えば、 と を使って、 が作れていれば、それに を加えることで を作ることができます。 そうすると、 行 列の真偽値は、 行 列の真偽値となります。
これらをまとめると、次の漸化式が作れます。
ここで、 のことまで考えると、漸化式は次のようになります。
プログラムを作る
まず、表を次のように初期化します。
その後、さきほど求めた漸化式に従って、表を更新していきます。
プログラムは次のようになります。計算量は、 です。
練習問題
動的計画法における有名問題であるナップサック問題を解いてみましょう。
ナップサック問題(ナップサックもんだい、Knapsack problem)は、計算複雑性理論における計算の難しさの議論の対象となる問題の一つで、 種類の品物(各々、価値 、重量 )が与えられたとき、重量の合計が を超えない範囲で品物のいくつかをナップサックに入れて、その入れた品物の価値の合計を最大化するには入れる品物の組み合わせをどのように選べばよいか」という整数計画問題である。同じ種類の品物を 1 つまでしか入れられない場合()や、同じ品物をいくつでも入れてよい場合( は 0 以上の整数)など、いくつかのバリエーションが存在する。 -- フリー百科事典『ウィキペディア(Wikipedia)』
ここでは、 とした、0-1 ナップサック問題を解いてみてください。
ヒント
まずは先ほどと同様に表を考えます。
として考えてみます。
行 列は、重さが 以下になるように 番目までの品物の中からいくつかをナップサックに入れたときの価値の最大値とします。
表は次のようになります。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
2 | 0 | 0 | 2 | 3 | 3 | 5 | 5 | 5 | 5 | 5 | 5 |
3 | 0 | 0 | 2 | 3 | 3 | 6 | 6 | 8 | 9 | 9 | 11 |