たとえば、フィボナッチ数列の反復バージョンと再帰バージョンを考えてみましょう。それらは同じ時間計算量を持っていますか?
答えは、実装によって大きく異なります。あなたが与えた例では、いくつかの可能な解決策があり、ソリューションを実装するための素朴な方法は、反復的に実装された場合、より複雑になると思います。 2つの実装は次のとおりです。
_int iterative_fib(int n) {
if (n <= 2) {
return 1;
}
int a = 1, b = 1, c;
for (int i = 0; i < n - 2; ++i) {
c = a + b;
b = a;
a = c;
}
return a;
}
int recursive_fib(int n) {
if (n <= 2) {
return 1;
}
return recursive_fib(n - 1) + recursive_fib(n-2);
}
_
両方の実装で、正しい入力、つまりn> = 1を想定しました。最初のコードははるかに長いですが、その複雑さはO(n)つまり線形ですが、2番目の実装は短いですが、指数関数的な複雑さを持っていますO(fib(n)) = O(φ^ n)(φ = (1+√5)/2
)なので、はるかに遅くなります。メモ化を導入することで、再帰バージョンを改善できます(つまり、覚えています)。すでに計算した関数の戻り値)これは通常、値を格納する配列を導入することによって行われます。例を次に示します。
_int mem[1000]; // initialize this array with some invalid value. Usually 0 or -1
// as memset can be used for that: memset(mem, -1, sizeof(mem));
int mem_fib(int n) {
if (n <= 2) {
return mem[n] = 1;
}
if (mem[n-1] == -1) {
solve(n-1);
}
if (mem[n-2] == -1) {
solve(n-2);
}
return mem[n] = mem[n-1] + mem[n-2];
}
_
ここで、再帰的アルゴリズムの複雑さは、反復解法と同じように線形です。上で紹介したソリューションは、問題の動的計画法ソリューションのトップダウンアプローチです。ボトムアップアプローチは、反復として紹介したソリューションと非常によく似たものになります。 wikipedia を含む動的計画法に関する記事がたくさんあります
私の経験で遭遇した問題によっては、ボトムアップアプローチ(つまり反復ソリューション)で解決するのがはるかに難しいものもあれば、トップダウンアプローチで解決するのが難しいものもあります。ただし、理論によれば、反復解を持つ各問題には、同じ計算の複雑さを持つ再帰があります(逆も同様です)。
この回答がお役に立てば幸いです。
Fibanocci級数を計算するための特定の再帰的アルゴリズムは効率が低くなります。再帰的アルゴリズムを介してfib(4)を見つける次の状況を考えてみましょう。
int fib(n) :
if( n==0 || n==1 )
return n;
else
return fib(n-1) + fib(n-2)
上記のアルゴリズムがn = 4で実行されると
fib(4)
fib(3) fib(2)
fib(2) fib(1) fib(1) fib(0)
fib(1) fib(0)
それは木です。 fib(4)を計算するには、fib(3)やfib(2)などを計算する必要があると書かれています。
4の値が小さい場合でも、fib(2)は2回計算され、fib(1)は3回計算されることに注意してください。この追加の数は、多数になると増加します。
Fib(n)の計算に必要な加算の数は次のように推測されます。
fib(n+1) -1
したがって、この重複は、この特定のアルゴリズムのパフォーマンス低下の原因となるものです。
フィボナッチ数列の反復アルゴリズムは、冗長なものを計算する必要がないため、かなり高速です。
ただし、すべてのアルゴリズムで同じ場合とは限りません。
再帰的アルゴリズムを使用する場合は、すべての関数ローカル変数を配列に格納することで反復に変換でき、ヒープ上のスタックを効果的にシミュレートできます。このように行われた場合、反復と再帰の間に違いはありません。
(少なくとも)2つの再帰的フィボナッチアルゴリズムがあることに注意してください。したがって、正確な例を得るには、話している再帰的アルゴリズムを指定する必要があります。
はい、すべての反復アルゴリズムは再帰バージョンに変換でき、その逆も可能です。 1つは継続を渡すことによる方法、もう1つはスタック構造を実装することによる方法です。これは、時間の複雑さを増すことなく行われます。
末尾再帰を最適化できれば、漸近的なメモリの複雑さを増すことなく、すべての反復アルゴリズムを再帰アルゴリズムに変換できます。
はい、アルゴリズムの基礎となるまったく同じアイデアを使用する場合、それは問題ではありません。ただし、再帰は反復に関して簡単に使用できることがよくあります。たとえば、ハノイの塔の再帰バージョンを作成するのは非常に簡単です。再帰バージョンを対応する反復バージョンに変換することは困難であり、実行できたとしてもエラーが発生しやすくなります。実際には、すべての再帰的アルゴリズムを同等の反復アルゴリズムに変換できるという定理があります(これを行うには、1つ以上のスタックデータ構造を使用して再帰を繰り返し模倣し、再帰呼び出しに渡されるパラメーターを保持する必要があります)。