回答:
分割統治
分割統治は、問題をサブ問題に分割し、各サブ問題を再帰的に征服し、これらのソリューションを組み合わせることで機能します。
動的プログラミング
動的プログラミングは、サブ問題が重複している問題を解決するための手法です。各サブ問題は1回だけ解決され、各サブ問題の結果は、将来の参照用にテーブル(通常は配列またはハッシュテーブルとして実装)に保存されます。これらのサブソリューションは、元のソリューションを取得するために使用できます。サブ問題のソリューションを保存する手法は、メモ化と呼ばれます。
あなたは考えるかもしれません DP = recursion + re-use
違いを理解するための典型的な例は、n番目のフィボナッチ数の取得に向けたこれらの両方のアプローチを見ることです。MITからこの資料を確認してください。
分割統治アプローチ
動的プログラミングのアプローチ
分割統治と動的プログラミングの他の違いは次のとおりです。
分割統治:
動的プログラミング:
場合によっては、再帰的にプログラミングするときに、同じパラメーターを使用して関数を複数回呼び出すことがありますが、これは不要です。
有名なフィボナッチ数の例:
index: 1,2,3,4,5,6...
Fibonacci number: 1,1,2,3,5,8...
function F(n) {
if (n < 3)
return 1
else
return F(n-1) + F(n-2)
}
F(5)を実行してみましょう:
F(5) = F(4) + F(3)
= {F(3)+F(2)} + {F(2)+F(1)}
= {[F(2)+F(1)]+1} + {1+1}
= 1+1+1+1+1
したがって、次のように呼びます。1回F(4)2回F(3)3回F(2)2回F(1)
動的プログラミングのアプローチ:同じパラメーターを使用して関数を複数回呼び出す場合は、結果を変数に保存して、次回から直接アクセスします。反復的な方法:
if (n==1 || n==2)
return 1
else
f1=1, f2=1
for i=3 to n
f = f1 + f2
f1 = f2
f2 = f
もう一度F(5)を呼び出しましょう:
fibo1 = 1
fibo2 = 1
fibo3 = (fibo1 + fibo2) = 1 + 1 = 2
fibo4 = (fibo2 + fibo3) = 1 + 2 = 3
fibo5 = (fibo3 + fibo4) = 2 + 3 = 5
ご覧のように、複数の呼び出しが必要なときはいつでも、対応する変数にアクセスして値を再計算するのではなく取得します。
ちなみに、動的プログラミングは、再帰的なコードを反復的なコードに変換することを意味するものではありません。再帰的なコードが必要な場合は、サブ結果を変数に保存することもできます。この場合、この手法はメモ化と呼ばれます。この例では、次のようになります。
// declare and initialize a dictionary
var dict = new Dictionary<int,int>();
for i=1 to n
dict[i] = -1
function F(n) {
if (n < 3)
return 1
else
{
if (dict[n] == -1)
dict[n] = F(n-1) + F(n-2)
return dict[n]
}
}
したがって、Divide and Conquerとの関係は、D&Dアルゴリズムが再帰に依存していることです。そして、それらの一部のバージョンには、「同じパラメーターの問題を持つ複数の関数呼び出し」があります。D&DアルゴのT(n)を改善するためにDPが必要な例については、「マトリックスチェーン乗算」および「最長共通サブシーケンス」を検索してください。
今のところ、動的プログラミングは分割統治パラダイムの延長であると言えます。
私はそれらをまったく異なるものとして扱いません。これらは両方とも、問題を同じまたは関連するタイプの2つ以上のサブ問題に再帰的に分解することによって機能するため、これらが直接解決できるほど単純になるまで。次に、副問題の解を組み合わせて、元の問題の解を与えます。
では、なぜ今でもパラダイム名が異なるのか、なぜ動的プログラミングを拡張機能と呼んだのか。これは、問題に特定の制限または前提条件がある場合にのみ、動的プログラミングアプローチを問題に適用できるためです。その後、動的プログラミングは、メモ化または集計手法を使用して、分割統治アプローチを拡張します。
少しずつ行きましょう…
今発見したように、動的プログラミングを適用するためには、問題を分割して征服する必要がある2つの重要な属性があります。
最適な部分構造 —部分問題の最適解から最適解を構築できます
重複するサブ問題 —問題は複数回再利用されるサブ問題に分解できます。または問題の再帰アルゴリズムが常に新しいサブ問題を生成するのではなく、何度も同じサブ問題を解決します
これらの2つの条件が満たされると、この分割統治問題は動的計画法を使用して解決できると言えます。
ダイナミックプログラミングアプローチは、分割統治アプローチを拡張し、パフォーマンスを大幅に改善する可能性のあるサブ問題ソリューションを保存して再利用する2つの手法(memoizationとtabulation)を備えています。たとえば、フィボナッチ関数の単純な再帰的な実装にはO(2^n)
、DPソリューションが時間だけで同じことを行う場合の時間の複雑さがありO(n)
ます。
メモ化(トップダウンキャッシュフィル)は、以前に計算された結果をキャッシュして再利用する手法を指します。したがって、メモ化されたfib
関数は次のようになります。
memFib(n) {
if (mem[n] is undefined)
if (n < 2) result = n
else result = memFib(n-2) + memFib(n-1)
mem[n] = result
return mem[n]
}
集計(ボトムアップキャッシュフィル)も同様ですが、キャッシュエントリのフィルに重点を置いています。キャッシュ内の値の計算は、反復的に行うのが最も簡単です。の集計バージョンは次のfib
ようになります。
tabFib(n) {
mem[0] = 0
mem[1] = 1
for i = 2...n
mem[i] = mem[i-2] + mem[i-1]
return mem[n]
}
メモ化と集計の比較の詳細については、こちらをご覧ください。
ここで把握しておく必要のある主な考え方は、分割統治問題にはサブ問題が重複しているため、サブ問題ソリューションのキャッシュが可能になり、メモ化/集計がシーンにステップアップするということです。
これでDPの前提条件とその方法論に精通したので、上記のすべてを1つの図にまとめる準備ができています。
コード例を確認したい場合は、ここで2つのアルゴリズム例を見つける詳細な説明をご覧ください。DPとDCの違いを示す2進検索と最小編集距離(Levenshtein距離)です。
これについてはすでにウィキペディアやその他の学術リソースを読んでいると思います。そのため、その情報は再利用しません。また、私は絶対にコンピュータサイエンスの専門家ではないことにも注意する必要がありますが、これらのトピックの理解について2セントを共有します...
問題を個別のサブ問題に分解します。フィボナッチ数列の再帰アルゴリズムは、最初にfib(n-1)を解くことによってfib(n)を解くため、動的プログラミングの例です。元の問題を解決するために、別の問題を解決します。
これらのアルゴリズムは通常、同様の問題を解決し、最後にまとめます。Mergesortは、分割統治の典型的な例です。この例とフィボナッチの例の主な違いは、マージソートでは、分割は(理論的には)任意であり、どのように分割しても、マージとソートが行われることです。配列をどのように分割しても、配列をマージソートするには同じ量の作業を行う必要があります。fib(52)を解くには、fib(2)を解くよりも多くのステップが必要です。
私はDivide & Conquer
、再帰的なアプローチとして、またDynamic Programming
テーブルの充填として考えています。
たとえば、Merge Sort
はDivide & Conquer
アルゴリズムです。各ステップと同様に、配列を2つの半分に分割し、2つの半分を再帰的に呼び出しMerge Sort
て、それらをマージします。
Knapsack
であるDynamic Programming
あなたは、全体のナップザックの部分問題に最適なソリューションを表すテーブルを埋めているようなアルゴリズム。表の各エントリは、アイテム1〜jが与えられた状態で、重量wのバッグに入れて持ち運べる最大値に対応しています。
分割統治には、再帰の各レベルで3つのステップが含まれます。
動的計画法には、次の4つのステップが含まれます
。1 .最適解の構造を特徴付けます。
2. 最適解の値を再帰的に定義します。
3. 最適解の価値を計算します。
4. 構築し最適解を計算された情報から。
理解を容易にするために、総当たりのソリューションとしての分割統治と、動的プログラミングとしての最適化を見てみましょう。
NBサブ問題が重複する分割統治アルゴリズムは、dpでのみ最適化できます。
fact(5) = 5* fact(4) = 5 * (4 * fact(3))= 5 * 4 * (3 *fact(2))= 5 * 4 * 3 * 2 * (fact(1))
上記のように、fact(x)は繰り返されないため、階乗には重複しない問題があります。
fib(5) = fib(4) + fib(3) = (fib(3)+fib(2)) + (fib(2)+fib(1))
上記のように、fib(4)とfib(3)はどちらもfib(2)を使用します。同様に、非常に多くのfib(x)が繰り返されます。そのため、フィボナッチにはサブ問題が重複しています。