2009-04-28 2 views

答えて

10

反復されるスタックの使用

recursion

参照を除去する標準的な技術であります多くの場合、再帰よりも速く/オーバーヘッドが少なくなります。再帰では、マシンのスタックを暗黙的にスタックとして使用します。これは "無償で"取得しますが、高価な関数呼び出し(およびアテンダントマシンスタック管理)のコストを払っています。

しかし、再帰的な関数は、しばしばより読み書きする方が直感的です。

多くの場合、再帰を使用して関数を記述し、ボトルネックになるまでその関数を残してから、明示的なスタックを使用する反復関数に置き換えることができます。

+1

+1 - 良い観察。チャーリーが述べるように、再帰には非常に自然な問題がいくつかあります。しかし、開発者は自分が行っているトレードオフを知る必要があることをよく指摘しています。 –

+2

それは必ずしもそうであることを除いて:これは古い妻の物語です。 Guy Steeleの論文を参照してください:http://portal.acm.org/citation.cfm?id=800055.802039 –

+0

@Charlie Martin:おそらく言うのはおそらく最も安全です:それは、どのような実装がコンパイラ/インタプリタ持っている。私は再帰がLispで速いことを確信しています。すべてがLispでの再帰です。もし速くなければ、それは重大な問題です。いつものように、それは依存しています。あなたが本当に何が速いかを知りたいのであれば、それをベンチマークします。 – cgp

15

いいえちょうど反対です。再帰は、すべてのクラスの問題を表現する自然な方法です。スタックは再帰を持たない場合にそれをシミュレートする方法です。

例えば、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で記述している場合は、それらの中間結果を追跡するために独自のスタックを管理する必要があります。

+0

私は、問題を表現する "自然な方法"である再帰に関してあなたに同意します(それに応じてあなたにアップします)。しかし、私はそれがもう少し計算上高価なので、tpdiもupvotedであることを認識したことがあります。 –

+3

それ以外は当てはまりません。いくつかの問題やいくつかの環境では計算機的に高価です。例えば、このプログラムは本当に高価です。一方、もう少し作業すれば、ここでは尻尾の再帰として表現されている可能性があります。http://triton.towson.edu/~akayabas/COSC455_Spring2000/Recursion_Iteration.htm、尾の再帰は反復より悪くありません頻繁に良くなるhttp://portal.acm.org/citation.cfm?id=800055.802039 –

6

には、魚釣りの修正が含まれています。 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 
    } 
} 
+0

あらゆる種類の再帰は、スタック構造を利用して繰り返し書き直すことができます。再帰は、問題を解決するためにコールスタックを利用する方法です。 しかし、末尾の再帰は、GOTOを使用して書き直すことができ、基本的に反復ループに変換されます。これが尾の再帰を排除する標準的な方法です。 – fishlips

4

テールコールがスタックを増やす(テールコール最適化(TCO)が適用されない)プログラミング言語/環境では、深い再帰を避けることが最善であり、スタックデータ構造を使用する可能性のある反復ソリューションは好ましい。

一方、テールコールを使用して反復をサポートする言語/環境にいる場合、または再帰の深さが常に小さい場合、再帰はしばしば洗練された/優雅なソリューションです。

(これは少しオーバー幅広いですが、全体的に、私は決して「時代遅れ」再帰を呼ぶだろう。)

+0

+1、私はあなたがどのように/再帰がより速くなることができるかを指摘したのが好きです。 – cgp

3

いやいや、私は現代の開発者は、いくつかの上にreadibilityとメンテナンスのしやすさを重視すべきだと思いますミリ秒。

問題が再帰的である場合は、再帰的に解決することをお勧めします。

さらに、反復/積み重ねの解決策を強制するために、いくつかの予期しないバグを導入する可能性があります。

+0

あなたは頭に爪があります。タスクに応じて適切なツールを選択する必要があります。しかし、ほとんどの可読性は、固定点で問題を表現する上で重要です。 – sibidiba

+0

あなたの仕事がクライアントと交渉された要件を満たさなければならないことは明らかです。プログラムの実行時間を短縮する必要がある場合は、実装の選択肢を確認する必要があります。 –

関連する問題