動的プログラミング(空間と時間を交換するアルゴリズム)の例と詳しい使い方の説明
このブログは、「アルゴリズム入門」の第 15 章にある動的プログラミング アルゴリズムに基づいており、書籍内の多くの内容と例を引用し、書籍内の擬似コードに基づいてPythonコードを再現し、コア ロジックを詳細に説明しています。およびアルゴリズムの実装プロセス。
ダイナミック プログラミング (DP) の考え方
動的計画法 (Dynamic Programming) アルゴリズムの核となる考え方は、大きな問題を重複するサブ問題に分割して解決し、それによって段階的に最適な解決策を得る処理アルゴリズムです。
動的プログラミングは、部分問題に対する解決策を組み合わせることによって元の問題を解決するという点で、分割統治法に似ています (ここでの「プログラミング」とは、コンピューター プログラムを書くことではなく、テーブル法を指します)。しかし、分割統治法では、問題を互いに素な部分問題に分割し、それらの部分問題を再帰的に解決し、それらの解を組み合わせて元の問題の解を見つけます。対照的に、動的計画法は、サブ問題が重複する場合、つまり、異なるサブ問題が共通のサブ問題を持つ場合に適用されます(サブ問題の解決は再帰的に実行され、より小さなサブサブ問題に分割されます)。
この場合、分割統治アルゴリズムは、共通のサブサブ問題を繰り返し解決することで、多くの不必要な作業を実行します。動的プログラミング アルゴリズムは、各サブサブ問題を 1 回だけ解決し、その解をテーブルに保存するため、サブサブ問題が解決されるたびに再計算する必要がなくなり、この不必要な計算作業が回避されます。
例
動的プログラミング手法は、最適化問題を解決するためによく使用されます。以下に 2 つの例を示します。1 つ目は、合計値が最大になるように棒鋼を短い棒鋼に切断することです。2 つ目は、最小値を使用する方法を解決することです。スカラー乗算演算 行列チェーン乗算演算を完了する
鉄筋切断の問題
鉄筋切断問題の背景には次のようなものがあります。
たとえば、下の図は、4 インチの棒鋼に対して考えられるすべての切断ソリューションを示しています。
棒鋼の上の数字は、棒鋼の各セクションに対応する価格です。切断ソリューションが異なれば、価格も異なります。長さ 4 インチの棒鋼を切断して、それぞれ長さ 2 インチの 2 本の棒鋼を製造すると、P 2 P_2が生成されることがわかりました。P2+ P2P_2P2=5+5=10 の収入が最適解であり、上図のプラン C に相当します。
次に、棒鋼切断問題の最適収益の再帰式を徐々に分析します。より一般的な収益公式は、長さ n r_n
の棒鋼を切断した後の最大収益です。rん次のように表現されます:
より単純な再帰的メソッド: where, pi p_i
in (15.2)p私は切断せずにテーブルから直接入手できる、長さ i の棒鋼に対応する価格を指します。
従来の再帰的手法で解くと、n のサイズが少し大きくなるとプログラムの実行時間が非常に長くなります。これは、このメソッドが同じパラメータ値で自分自身を再帰的に繰り返し呼び出す、つまり同じサブを繰り返し解くためです。 -問題。時間は指数関数的に増加します。以下の図は、n=4 の場合の再帰ツリーの呼び出しプロセスを示しています。
動的計画法の実行時間は多項式次数です。
動的プログラミングには同等の実装方法が 2 つあります:ボトムアップ方法のコード実装プロセス
を以下に示します
:上記の疑似コードでは、入力は 2 つの変数、価格リスト配列と長さ n です; 出力は鋼棒です長さ n で最適な利益が得られます。
長さが 9 の場合の棒鋼切断による最大利益を計算します。
import numpy as np
def Bottom_Up_Cut_Rod(p, n):
r = []
r.insert(0, 0)
for j in range(1, n + 1):
q = float('-inf')
for i in range(1, j + 1):
q = max(q, p[i - 1] + r[j - i])
r.insert(j, q)
return r[n]
if __name__ == '__main__':
# 出售长度为i(i=1,2,3,,,10)的钢条所对应的价格样表
p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
n = 9
result = Bottom_Up_Cut_Rod(p, n)
print("长度为{}时,".format(n))
print("对应的最优收益值为{}。".format(result))
下の図は、長さ 1、2、3... に対応する最適な切断計画と利益を示しています。
アイデア: サイズ n の元の問題を解決するには、最初にまったく同じ形式で小さいサイズの部分問題を解決します。つまり、最初の切断が完了した後、棒鋼の 2 つのセクションを棒鋼切断問題の 2 つの独立したインスタンスとみなします。関連する 2 つのサブ問題の最適解を組み合わせ、考えられるすべての 2 セクション切断ソリューションの中から総合利益が最も大きいものを選択することにより、元の問題に対する最適解を形成します。
鉄筋切断問題は最適な基礎構造特性を満たしていると言えます。つまり、問題の最適解は関連する部分問題の最適解で構成され、これらの部分問題は独立して解決できます。
行列連鎖乗算問題
行列連鎖乗算問題は、複数の行列を乗算して最速の計算順序を見つける問題です。
マトリックス チェーンに括弧を追加する方法は、製品の運用コストに大きな影響を与えます。次の例は、それを示しています。
したがって、A が pXq の行列で、B が qXr の行列である場合、積 C は pXr の行列になります。C の計算に必要な時間は、スカラー倍算の数、つまり pqr によって決まります。以下では、計算コストをスカラー乗算の回数で表します。
従来のアプローチ:実行時間は依然として指数関数的に増加するため、考えられるすべてのブラケット ソリューションを網羅しても効率的なアルゴリズムとは言えません。
行列連鎖問題の計算コスト式は以下のように定義され、計算行列A i , ..., j A_{i,...,j} をm[i,j] とします。あ私、... 、 j必要なスカラー乗算の最小数。
最適な分割点 k が不明であると仮定すると、再帰的な解の公式は次のようになります。
以下は、説明するための例です。
上の図は、理解を助けるために私が書いた例です。その中で、A1A_1あ1:2 × \回× 3、A 1 A_1あ1の次元は 2 行 3 列で、P リストには 4 つの行列の次元が格納されます (前の行列の列数は後の行列の行数に等しい)。A 1 A_1を要求するあ1A2A_2あ2A3A_3あ3A4A_4あ4計算コストは、( (1あ2) ( A3あ4))。其中, ( A 1 A 2 ) (A_1A_2) ( A1あ2)の計算コストは、上図の行列 C( A 3 A 4 ) (A_3A_4)( A3あ4)は上図の行列 D に相当し、最後に C と D を掛けます。最終的な計算コスト (合計回数) は 48 です。
以下に示すプロセス MATRIX-CHAIN-ORDER は、ボトムアップ テーブル方式を実装します。
各入力変数の意味は次のとおりです。
出力変数は、最適コスト行列 m と最適分割点行列 k です。
理解を助けるために、コードの計算プロセスを以下に示します。n=4、p = [ p 0 , p 1 , p 2 , p 3 , p 4 ] p = [p_0,p_1,p_2,p_3,p_4] と仮定します。p=[ p0、p1、p2、p3、p4],求A 1 A 2 A 3 A 4 A_1A_2A_3A_4あ1あ2あ3あ4最適なコストは m[1,4] を見つけることです。計算プロセスは次のとおりです。
左側は最適コスト行列、右側は最適分割点行列です。
図 15-5 のデータを例にとると、Python コードは次のとおりです。
import numpy as np
def MATRIX_CHAIN_ORDER(p):
n = len(p) - 1
# 定义计算代价矩阵m
m = np.zeros((n, n))
# 定义最优分割点矩阵s
s = np.zeros((n, n))
for l in range(2, n + 1):
for i in range(1, n - l + 2):
j = i + l - 1
m[i - 1, j - 1] = float('inf')
for k in range(i, j):
q = m[i - 1, k - 1] + m[k, j - 1] + p[i - 1] * p[k] * p[j]
if q < m[i - 1, j - 1]:
m[i - 1, j - 1] = q
s[i - 1, j - 1] = k
print(m) # 每个元素对应长度为j-i+1的矩阵所需要最小计算代价
print(s) # 对应长度为j-i+1的矩阵最优分割点
return m, s
if __name__ == '__main__':
p = [30, 35, 15, 5, 10, 20, 25]
MATRIX_CHAIN_ORDER(p)
実行結果は次のとおりです。
結果は、本の 2 つの行列とまったく同じであることがわかります。(本では行列が主対角線に沿って回転しているだけです)
アイデア: 自明ではない行列チェーン乗算問題インスタンスに対するすべての解決策にはチェーン分割が必要であり、最適な解決策はサブ問題インスタンスに対する最適な解決策で構成されます。したがって、行列連鎖乗算問題のインスタンスに対する最適解を構築するには、問題を 2 つの部分問題 ( A i A i + 1 ⋅ ⋅ ⋅ A k A_iA_{i+1}....A_k)に分割できます。あ私はあ私+ 1⋅⋅⋅あk和 A k + 1 ⋅ ⋅ ⋅ A j A_{k+1}···A_j あk + 1⋅⋅⋅あj) サブ問題インスタンスに対する最適解を求め、サブ問題に対する最適解を組み合わせます。分割点を決定する際には、最適なソリューションを見逃さないように、考えられるすべての分割点を確実に検討する必要があります。
アプリケーションが満たす条件とシナリオ
動的計画法によって解決される最適化問題には、最適部分構造と部分問題の重複という2 つの要素が必要です。
動的プログラミング アルゴリズムは、最適化問題を解決するために使用できます。この記事で示した 2 つの例に加えて、一般的なシナリオには、フィボナッチ数列の解決、最長共通部分列の解決、01 ナップザック問題、最適二分探索ツリーなどが含まれます。このアルゴリズムの原理と応用シナリオを詳しく理解したい場合は、「アルゴリズム入門」を読むことができます。私は電子版を持っています。必要な兄弟姉妹は私にプライベート メッセージを送って入手してください。 。
要約すると、最適な部分構造と部分問題の重複する性質を満たす問題は、動的計画法のアイデアを使用してモデル化できます。この方法では、メモリが解放され、以前の計算結果が保存され、次回発生したときに計算を繰り返すことなく直接呼び出されます。 . , したがって、時間を大幅に節約できます。これは、空間と時間を交換する古典的なアルゴリズムです。ほとんどの場合、プロジェクトでは、メモリ使用量よりもはるかに緩やかな時間要件が厳しくなる傾向があります。