可能性のある複製:
動的プログラミングとメモ化:トップダウン方式とボトムアップ方式
私はこれについて多くの記事を読みましたが、それを理解することはできません。再帰と動的プログラミングは同じように見えることもあれば、メモ化と動的プログラミングが似ていることもあります。誰かが私に違いを説明できますか?
追伸また、同じ問題に対して3つのアプローチを使用して、いくつかのコードを教えていただければ助かります。 (たとえば、フィボナッチ数列の問題、私が読んだすべての記事は再帰を使用していましたが、動的プログラミングと呼ばれていました)
フィボナッチ数列の計算を検討してください。
純粋な再帰:
int fib(int x)
{
if (x < 2)
return 1;
return fib(x-1) + fib(x-2);
}
指数関数的な呼び出し回数になります。
メモ化/ DPを使用した再帰:
void fib(int x)
{
static vector<int> cache(N, -1);
int& result = cache[x];
if (result == -1)
{
if (x < 2)
result = 1;
else
result = fib(x-1) + fib(x-2);
}
return result;
}
これで、最初の呼び出し数は線形になり、その後は一定になりました。
上記の方法は「遅延」と呼ばれます。最初に要求されたときに、以前の用語を計算します。
以下もDPと見なされますが、再帰はありません。
int fibresult[N];
void setup_fib()
{
fibresult[0] = 1;
fibresult[1] = 1;
for (int i = 2; i < N; i++)
fibresult[i] = fibresult[i-1] + fibresult[i-2];
}
int fib(int x) { return fibresult[x]; }
この方法は、「熱心」、「プリキャッシング」、または「反復」として説明される場合があります。全体的に高速ですが、サブ問題を計算する必要がある順序を手動で把握する必要があります。これはフィボナッチにとっては簡単ですが、より複雑なDP問題では難しくなり、高速の場合は遅延再帰法に戻ります十分な。
また、以下は再帰でもDPでもありません。
int fib(int x)
{
int a = 1;
int b = 1;
for (int i = 2; i < x; i++)
{
a = a + b;
swap(a,b);
}
return b;
}
一定の空間と線形時間を使用します。
また、完全性のために、ネザー再帰もDPも使用するフィボナッチの閉じた形式があり、黄金比に基づく数学式を使用してフィボナッチ項を一定時間で計算できるようになります:
http://www.dreamincode.net/forums/topic/115550-fibonacci-closed-form/
さて、再帰+メモ化は、正確に動的プログラミング:トップダウンアプローチに従った動的プログラミングの特定の「フレーバー」です。
より正確には、具体的にrecursionを使用する必要はありません。メモ化と組み合わせた分割統治ソリューションは、トップダウンの動的プログラミングです。 (再帰はLIFO分割と征服のフレーバーですが、FIFO分割と征服または他の種類の分割と征服も使用できます)。
だからそれは言う方が正しい
divide & conquer + memoization == top-down dynamic programming
また、非常に正式な観点から、部分的な反復解を生成しない問題に対して分割統治ソリューションを実装すると(メモ化にはメリットがないことを意味します)、この分割統治ソリューションは「動的プログラミング」の縮退した例。
ただし、動的プログラミングはより一般的な概念です。ダイナミックプログラミングでは、分割&征服+メモ化とは異なるボトムアップアプローチを使用できます。
インターネットで詳細な定義を見つけることができると確信しています。物事を単純化するための私の試みがここにあります。
再帰は再び自分自身を呼び出しています。
動的計画法は、特定の構造(最適なサブ構造)を示す問題を解決する方法であり、問題を元の問題に似たサブ問題に分解できます。明らかに、再帰を呼び出してDPを解決できます。しかし、それは必要ではありません。再帰なしでDPを解くことができます。
メモ化は、再帰に依存するDPアルゴリズムを最適化する方法です。ポイントは、すでに解決されているサブ問題を再度解決することではありません。サブ問題の解決策のキャッシュとして表示できます。
それらは異なる概念です。それらはかなり頻繁に重なりますが、異なっています。
再帰は、関数がそれ自体を直接または間接的に呼び出すたびに発生します。これですべてです。
例:
a -> call a
a -> call b, b -> call c, c -> call a
動的プログラミング は、より大きな問題を解決するために、より小さな副問題の解決策を使用する場合です。通常、このようなソリューションは再帰関数の観点から考えるため、これは再帰的に実装するのが最も簡単です。ただし、時間とメモリが少なくて済むため、通常は反復実装が推奨されます。
メモ化は、再帰的なDP実装が必要以上に時間がかかるのを防ぐために使用されます。ほとんどの場合、DPアルゴリズムは、複数の大きな問題を解決する際に同じ副問題を使用します。再帰的な実装では、これは同じことを複数回再計算することを意味します。メモ化は、これらの副問題の結果をテーブルに保存することを意味します。再帰呼び出しを入力するとき、結果がテーブルに存在するかどうかを確認します。存在する場合は返し、存在しない場合は計算し、テーブルに保存してから返します。
再帰は、メモ化や動的プログラミングとはまったく関係ありません。それは完全に別の概念です。
それ以外の場合、これは次の重複した質問です。 動的プログラミングとメモ化:ボトムアップとトップダウンのアプローチ