リリース・改善中
Algorithm
動的計画法(DP)は重なり合う部分問題の答えを記録して再利用する手法で、指数時間かかる探索を表の計算に置き換え、最適化や数え上げを効率よく解きます。
動的計画法(Dynamic Programming、DP)は、問題を小さな部分問題に分け、それぞれを一度だけ解いて答えを記録し、同じ部分問題が再び現れたら記録した答えを使い回すアルゴリズム設計手法です。1950年代にリチャード・ベルマンが体系化しました。部分問題が重なり合い(重複部分問題)、全体の最適解が部分問題の最適解から組み立てられる(最適部分構造)ときに使えます。
単純な再帰や全探索では指数時間かかる問題も、DPを使えば状態数に比例する時間で解けるようになります。ファイル比較の diff、スペルチェックの編集距離、DNA配列のアラインメント、音声認識のビタビアルゴリズム、データベースの結合順序の決定など、実際のシステムの多くの場所で使われており、コーディング面接や競技プログラミングでも特によく出題されるテーマです。
学習はフィボナッチ数列でメモ化(トップダウン)と表の更新(ボトムアップ)の違いをつかむところから始め、硬貨の両替、0/1ナップサック問題、最長増加部分列(LIS)、最長共通部分列(LCS)、編集距離といった定番問題へ進むのがおすすめです。どの問題でも、まず状態・漸化式・初期条件・計算順序を書き出し、小さな表を手で埋めて漸化式を確かめてからコードに移しましょう。
同じ部分問題が何度も現れるとき、一度だけ計算して記録しておくことで、繰り返しの計算を表の参照に置き換えます。
全体の最適解を部分問題の最適解から組み立てられることが必要で、この性質が漸化式の根拠になります。
再帰にキャッシュを付けるトップダウン(functools.cache)と、ループで表を埋めるボトムアップの二通りで実装できます。
答えを決めるのに必要最小限のパラメータで状態を定め、直前の行だけ使う場合は配列を使い回してメモリを減らします。
knapsack は0/1ナップサック問題を1行分の表で解くボトムアップ実装で、容量を大きい方から回すことで各品物を高々一度しか使わないようにしています。lcs は2つの文字列の最長共通部分列の長さを functools.cache でメモ化したトップダウン実装です。python dynamic_programming.py を実行すると 9 と 4 が出力されます。
dynamic_programming.py
from functools import cache
def knapsack(weights, values, capacity):
"""0/1 knapsack: best total value within capacity (bottom-up, one row)."""
best = [0] * (capacity + 1)
for w, v in zip(weights, values):
for c in range(capacity, w - 1, -1): # high to low: each item used at most once
best[c] = max(best[c], best[c - w] + v)
return best[capacity]
def lcs(a, b):
"""Length of the longest common subsequence (top-down, memoized)."""
@cache
def solve(i, j):
if i == len(a) or j == len(b):
return 0
if a[i] == b[j]:
return 1 + solve(i + 1, j + 1)
return max(solve(i + 1, j), solve(i, j + 1))
return solve(0, 0)
print(knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)) # 9
print(lcs("ABCBDAB", "BDCABA")) # 4
python dynamic_programming.pyインストールから 動的計画法 の中心となる考え方まで、6 章で順を追って学びます。
動的計画法 について質問し、経験を共有し、意見を交わす場所です。
まだディスカッションはありません。最初の話題を始めましょう。
コメント 0件
ログイン · ログインするとコメントできます。
最初のコメントを書いてみましょう。