分割統治アルゴリズムと動的計画法アルゴリズムの違いは何ですか? 2つの用語はどう違うのですか?私はそれらの違いを理解していません。
簡単な例を挙げて、2つの違いと、それらが類似していると思われる理由を説明してください。
分割統治
分割統治は、問題をサブ問題に分割し、各サブ問題を再帰的に克服し、これらのソリューションを組み合わせることにより機能します。
ダイナミックプログラミング
動的計画法は、重複する副問題を伴う問題を解決するための手法です。各サブ問題は1回だけ解決され、各サブ問題の結果は将来の参照用にテーブル(一般に配列またはハッシュテーブルとして実装)に保存されます。これらのサブソリューションは、元のソリューションを取得するために使用でき、サブ問題ソリューションを保存する技術はメモ化として知られています。
DP = recursion + re-use
について考えるかもしれません
違いを理解する古典的な例は、n番目のフィボナッチ数を取得するためのこれらのアプローチを両方とも見ることです。これを確認してください material MITから。
分割統治アプローチ
ダイナミックプログラミングアプローチ
分割統治と動的プログラミングのもう1つの違いは次のとおりです。
分割統治:
動的プログラミング:
再帰的にプログラミングする場合、同じパラメーターを使用して関数を複数回呼び出すことがありますが、これは不要です。
有名なフィボナッチ数の例:
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]
}
}
したがって、分割統治との関係は、D&Dアルゴリズムが再帰に依存するということです。そして、それらのいくつかのバージョンには、この「同じパラメーターの問題を伴う複数の関数呼び出し」があります。 D&DアルゴリズムのT(n)を改善するためにDPが必要な例については、「マトリックスチェーン乗算」と「最長共通サブシーケンス」を検索してください。
今のところ私が見ているように、私は動的プログラミングは分割統治パラダイムの拡張であると言える。
私はそれらを完全に異なるものとして扱いません。 両方とも問題を再帰的に2つ以上の副問題に分解することで機能する同じまたは関連するタイプであり、これらが直接解決できるほど単純になるまで。次に、サブ問題の解決策を組み合わせて、元の問題の解決策を提供します。
それで、なぜ私たちはまだ異なるパラダイム名を持っているのか、なぜ動的プログラミングを拡張と呼んだのか。これは、動的プログラミングアプローチが問題に適用される可能性があるためです問題に特定の制限または前提条件がある場合のみ。その後、動的プログラミングはmemoizationまたはtabulationテクニックで分割統治アプローチを拡張します。
順を追って…
発見したばかりのように、動的プログラミングを適用するには、問題を分割して克服する必要がある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]
}
Tabulation(bottom-up cache fill)も似ていますが、キャッシュのエントリを埋めることに焦点を当てています。キャッシュ内の値の計算は、繰り返し実行するのが最も簡単です。 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セントを共有します...
問題を個別のサブ問題に分解します。フィボナッチ数列の再帰アルゴリズムは、最初にfib(n-1)を解くことでfib(n)を解くため、動的プログラミングの例です。元の問題を解決するために、different問題を解決します。
これらのアルゴリズムは通常、問題の同様の部分を解決し、最後にまとめます。 Mergesortは、分割統治の典型的な例です。この例とフィボナッチの例の主な違いは、マージソートでは、(理論的には)分割が任意であり、どのようにスライスしても、マージとソートが行われることです。配列をどのように分割しても、同じ量のworkを実行して配列をマージソートする必要があります。 fib(52)を解くには、fib(2)を解くよりもステップ数が必要です。
Divide & Conquer
は再帰的なアプローチであり、Dynamic Programming
はテーブルの充填であると考えています。
たとえば、Merge Sort
はDivide & Conquer
アルゴリズムです。各ステップで、配列を2つの半分に分割し、2つの半分に対してMerge Sort
を再帰的に呼び出してから、それらをマージします。
Knapsack
はDynamic Programming
アルゴリズムです。これは、ナップザック全体のサブ問題に対する最適なソリューションを表すテーブルを埋めているためです。表の各エントリは、アイテム1〜jが与えられた重量wのバッグに入れて持ち込める最大値に対応しています。