私は、人々が再帰の代わりにスタックを使うことを選択しているいくつかの場所で読んできました。これは、再帰が古くなった仕事の仕方であるとみなされるか、あるいは両方の方法が異なるコンテキストで同等に適用できるためですか?再帰は一般的に、スタックを使用する場合と比較して、古い方法のトラバースと見なされますか?
答えて
反復されるスタックの使用
参照を除去する標準的な技術であります多くの場合、再帰よりも速く/オーバーヘッドが少なくなります。再帰では、マシンのスタックを暗黙的にスタックとして使用します。これは "無償で"取得しますが、高価な関数呼び出し(およびアテンダントマシンスタック管理)のコストを払っています。
しかし、再帰的な関数は、しばしばより読み書きする方が直感的です。
多くの場合、再帰を使用して関数を記述し、ボトルネックになるまでその関数を残してから、明示的なスタックを使用する反復関数に置き換えることができます。
いいえちょうど反対です。再帰は、すべてのクラスの問題を表現する自然な方法です。スタックは再帰を持たない場合にそれをシミュレートする方法です。
例えば、questionを参照してください。あるいは、標準の再帰関数のようなものを考えてみましょう。n番目のフィボナッチ数を計算する。
あなたはFibonacci numbersシリーズF N = F のn-1 + F N-2ように定義された
0,1,1,2,3,5,8,13, ...
であることを思い出します。
F(0)= 0
F(1)= 1
再帰的ステップ:
F(N)= F(
この
はベースケースとして再帰的定義のように書くことができます。 F(0)= 0、F(1)= 1、F(2)= F(0)+ F(1)= 1であり、等々。
(ちょうどにやにや笑いのためにCで)これを計算するための簡単なプログラムです。このプログラムは、元の定義に対応する方法を密接に
int fib(int n) {
/* we'll ignore possible negative arguments, see Wikipedia */
switch(n) {
case 0: return 0; break;
case 1: return 1; break;
default: return fib(n-1)+fib(n-2); break;
}
}
お知らせ?
事実、Cは呼び出しスタックのすべての中間結果を管理します。いくつかの言語はそれをするために定義されていません(私はオフハントと考えられる唯一のものは古いFORTRANですが、他にもあると確信しています)。アセンブラまたは古いFORTRANで記述している場合は、それらの中間結果を追跡するために独自のスタックを管理する必要があります。
私は、問題を表現する "自然な方法"である再帰に関してあなたに同意します(それに応じてあなたにアップします)。しかし、私はそれがもう少し計算上高価なので、tpdiもupvotedであることを認識したことがあります。 –
それ以外は当てはまりません。いくつかの問題やいくつかの環境では計算機的に高価です。例えば、このプログラムは本当に高価です。一方、もう少し作業すれば、ここでは尻尾の再帰として表現されている可能性があります。http://triton.towson.edu/~akayabas/COSC455_Spring2000/Recursion_Iteration.htm、尾の再帰は反復より悪くありません頻繁に良くなるhttp://portal.acm.org/citation.cfm?id=800055.802039 –
には、魚釣りの修正が含まれています。 What is tail-recursion?
末尾再帰(すなわち、反復を使用して除去することができる)の例:
public class TailTest
{
public static void Main()
{
TailTest f = new TailTest();
f.DoTail(0);
}
public void DoTail(int n)
{
int v = n + 1;
System.Console.WriteLine(v);
DoTail(v); // Tail-Recursive call
}
}
あらゆる種類の再帰は、スタック構造を利用して繰り返し書き直すことができます。再帰は、問題を解決するためにコールスタックを利用する方法です。 しかし、末尾の再帰は、GOTOを使用して書き直すことができ、基本的に反復ループに変換されます。これが尾の再帰を排除する標準的な方法です。 – fishlips
テールコールがスタックを増やす(テールコール最適化(TCO)が適用されない)プログラミング言語/環境では、深い再帰を避けることが最善であり、スタックデータ構造を使用する可能性のある反復ソリューションは好ましい。
一方、テールコールを使用して反復をサポートする言語/環境にいる場合、または再帰の深さが常に小さい場合、再帰はしばしば洗練された/優雅なソリューションです。
(これは少しオーバー幅広いですが、全体的に、私は決して「時代遅れ」再帰を呼ぶだろう。)
+1、私はあなたがどのように/再帰がより速くなることができるかを指摘したのが好きです。 – cgp
いやいや、私は現代の開発者は、いくつかの上にreadibilityとメンテナンスのしやすさを重視すべきだと思いますミリ秒。
問題が再帰的である場合は、再帰的に解決することをお勧めします。
さらに、反復/積み重ねの解決策を強制するために、いくつかの予期しないバグを導入する可能性があります。
あなたは頭に爪があります。タスクに応じて適切なツールを選択する必要があります。しかし、ほとんどの可読性は、固定点で問題を表現する上で重要です。 – sibidiba
あなたの仕事がクライアントと交渉された要件を満たさなければならないことは明らかです。プログラムの実行時間を短縮する必要がある場合は、実装の選択肢を確認する必要があります。 –
- 1. 再帰とスタックを使用しないファイルツリーをトラバースする
- 2. 再帰的降下と再帰的上昇解析の比較
- 3. 再帰的ドメインとサブドメインの比較?
- 4. 動的にリストアアダプタを使用する場合と比較する場合
- 5. 反射を使用した一般的なパラメータタイプの比較
- 6. containsを使用して一般的なarraylistの参照を比較する
- 7. バッチ - 使用している場合は、他のforfilesと再帰的に
- 8. リフレクションを使用した一般的な比較者
- 9. 2つのディレクトリを再帰的に比較すると、シェルスクリプト
- 10. 比較方法は一般契約に違反しています。マップの比較に基づいて
- 11. スウィフト:一般的な構造体の再帰的使用
- 12. 2セルの一般的な比較
- 13. 一般的な値の比較
- 14. 使用して一般的な方法
- 15. 2つの一般的な値を比較する最良の方法は?
- 16. 式/ lambdaを使用して2つのリストを比較/フィルタする一般的な方法
- 17. 複数の特定のインデックスと複数の一般的なインデックスを比較する場合
- 18. は、メソッドのスロー例外の比較:比較の方法は、その一般的な契約に違反し
- 19. Clojure:furの名前による再帰と再帰の比較
- 20. 再帰的にバイナリツリーをトラバースする
- 21. 比較方法は一般契約に違反しています! (TimSort)
- 22. Javaエラー:「比較方法は一般契約に違反しています!
- 23. Pythonの再帰と例外、再帰的スタックを失うことなく例外をキャッチする方法
- 24. yoyはノード内の2つのディレクトリを再帰的に比較する方法
- 25. のMySQL、再帰的にそれらを比較する
- 26. ラムダ式を使用したKeyValueペアの一般的なリストをトラバースします
- 27. 方法で、一般的なパラメータで定義される一般的な復帰方法
- 28. トラバース再帰的jsonツリーとマージ
- 29. 一般的に使用されている方法の良い場所はどこですか?
- 30. 再帰は一般的に悪い習慣ですか?
+1 - 良い観察。チャーリーが述べるように、再帰には非常に自然な問題がいくつかあります。しかし、開発者は自分が行っているトレードオフを知る必要があることをよく指摘しています。 –
それは必ずしもそうであることを除いて:これは古い妻の物語です。 Guy Steeleの論文を参照してください:http://portal.acm.org/citation.cfm?id=800055.802039 –
@Charlie Martin:おそらく言うのはおそらく最も安全です:それは、どのような実装がコンパイラ/インタプリタ持っている。私は再帰がLispで速いことを確信しています。すべてがLispでの再帰です。もし速くなければ、それは重大な問題です。いつものように、それは依存しています。あなたが本当に何が速いかを知りたいのであれば、それをベンチマークします。 – cgp