再帰と反復


109

再帰が使用されるすべての場所でforループを使用できると言うのは正しいですか?そして、再帰が通常遅い場合、forループ反復でこれを使用する技術的な理由は何ですか?

そして、再帰をforループに変換することが常に可能な場合、それを行うための経験則はありますか?


3
recursioniterationiteration = for loopおもう。
gongzhitaao 2013年

4
Tom Moertelのブログには、再帰コードを反復コードに変換することに関する4つの優れた投稿があります。blog.moertel.com
tags

回答:


146

すべての関数呼び出しは、呼び出し元の関数に戻ることができるようにスタックに格納する必要があるため、通常、再帰ははるかに遅くなります。多くの場合、スコープ分離を実装するには、メモリを割り当ててコピーする必要があります。

テールコール最適化などのいくつかの最適再帰を高速化しますが、常に可能であるとは限らず、すべての言語で実装されているわけではありません。

再帰を使用する主な理由は、

  • 多くの場合、問題のアプローチを模倣すると、より直感的になります。
  • ツリーのような一部のデータ構造は、再帰を使用して探索する方が簡単です(またはいずれにしてもスタックが必要です)。

もちろん、すべての再帰一種のループとしてモデル化できます。それがCPUが最終的に行うことです。そして再帰自体は、より直接的には、関数呼び出しとスコープをスタックに入れることを意味します。ただし、再帰アルゴリズムをループするアルゴリズムに変更すると、多くの作業が必要になり、コードの保守性が低下する可能性があります。すべての最適化について、プロファイリングまたは証拠によって必要であることが示された場合にのみ試行する必要があります。


10
補足すると、再帰は、多くのアルゴリズムやCS全体で中心的な役割を果たす還元の用語と密接に関連しています。
SomeWittyUsername 2013年

3
再帰によってコードの保守性が向上する例を教えてください。私の経験では、それは常に逆です。ありがとう
Yeikel 2018

@Yeikel f(n)n番目のフィボナッチ数を返す関数を記述します。
Matt

54

再帰が使用されるすべての場所でforループを使用できると言うのは正しいですか?

はい、ほとんどのCPUの再帰はループとスタックデータ構造でモデル化されているためです。

そして、再帰が通常遅い場合、それを使用する技術的な理由は何ですか?

「通常は遅い」わけではありません。誤って適用されるのは再帰であり、遅いです。その上、最近のコンパイラーは、要求せずに一部の再帰をループに変換するのが得意です。

そして、再帰をforループに変換することが常に可能な場合、それを行うための経験則はありますか?

反復的に説明するときに最もよく理解されるアルゴリズムの反復プログラムを作成します。再帰的に最もよく説明されるアルゴリズムの再帰プログラムを記述します。

たとえば、多くのプログラミング言語でのバイナリツリーの検索、クイックソートの実行、式の解析については、再帰的に説明されることがよくあります。これらも再帰的にコーディングするのが最適です。一方、階乗の計算とフィボナッチ数の計算は、反復の観点から説明する方がはるかに簡単です。それらに再帰を使用することは、大ハンマーでハエを叩くようなものです:大ハンマーがそれで本当に良い仕事をするときでも、それは良い考えではありません+


+私はダイクストラの「プログラミングの規律」から大槌のアナロジーを借りた。


7
再帰は通常、スタックフレームなどを作成するため、よりコストがかかります(メモリが遅い/メモリが多い)。十分に複雑な問題に正しく適用された場合、違いは小さいかもしれませんが、それでもなお高価です。テール再帰の最適化などの例外が発生する可能性があります。
Bernhard Barker

すべてのケースで単一のforループについてはわかりません。より複雑な再帰、または複数の変数を含む再帰を考えてみましょう
SomeWittyUsername

@dasblinkenlight複数のループを単一のループに減らすことは理論的には可能かもしれませんが、これについては不明です。
SomeWittyUsername 2013年

@icepackはい、可能です。それはきれいではないかもしれませんが、それは可能です。
Bernhard Barker

私はあなたの最初の声明に同意するかわかりません。CPU自体は、実際には再帰をモデル化していません。再帰をモデル化するのは、CPUで実行されている命令です。第2に、ループ構造には動的に拡大および縮小するデータセットが(必要に応じて)ありません。この場合、再帰アルゴリズムは通常、再帰が実行する必要のある各レベルの深いレベルに対応します。
トランペットリック、

28

質問 :

そして、もし再帰が通常遅い場合、ループ反復のために再帰を使用する技術的な理由は何ですか?

答え:

アルゴリズムによっては、繰り返し解くことが難しいためです。深さ優先検索を再帰的および反復的に解決するようにしてください。反復でDFSを解決するのは非常に難しいという考えがわかります。

試してみるもう1つの良い点:Mergeの並べ替えを反復的に記述してみてください。かなり時間がかかります。

質問 :

再帰が使用されるすべての場所でforループを使用できると言うのは正しいですか?

答え:

はい。このスレッドは、に対する非常に良い答えがあります。

質問 :

そして、再帰をforループに変換することが常に可能な場合、それを行うための経験則はありますか?

答え:

私を信じて。独自のバージョンを作成して、深さ優先検索を繰り返し解決してください。一部の問題は、再帰的に解決する方が簡単であることがわかります。

ヒント:分割統治法で解決できる問題を解決する場合は、再帰が適しています。


3
信頼できる回答を提供する試みに感謝します。著者は賢明だと確信していますが、「信頼してください」は、答えがすぐに明らかではない意味のある質問に対する有用な回答ではありません。反復的な深さ優先検索を行うための非常に単純なアルゴリズムがあります。疑似コードのアルゴリズムの説明については、このページの下部にある例を参照してください:csl.mtu.edu/cs2321/www/newLectures/26_Depth_First_Search.html
jdelman

3

再帰は低速であるだけでなく、その深さによってはスタックオーバーフローエラーが発生する可能性もあります。


3

反復を使用して同等のメソッドを作成するには、明示的にスタックを使用する必要があります。反復バージョンのソリューションにはスタックが必要であるという事実は、問題が再帰の恩恵を受けることができるほど難しいことを示しています。原則として、再帰は、固定量のメモリでは解決できないため反復的に解決するときにスタックが必要になる問題に最適です。そうは言っても、再帰と反復は、異なるパターンに従っても同じ結果を示す可能性があります。どちらの方法がより適切に機能するかを判断するのはケースバイケースであり、ベストプラクティスは、問題が続くパターンに基づいて選択することです。

たとえば、三角形シーケンスのn番目の三角形番号を見つけるには、次のようにします。1 3 6 10 15…反復アルゴリズムを使用してn番目の三角形番号を見つけるプログラム:

反復アルゴリズムを使用する:

//Triangular.java
import java.util.*;
class Triangular {
   public static int iterativeTriangular(int n) {
      int sum = 0;
      for (int i = 1; i <= n; i ++)
         sum += i;
      return sum;
   }
   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                            iterativeTriangular(n));
   }
}//enter code here

再帰アルゴリズムを使用する:

//Triangular.java
import java.util.*;
class Triangular {
   public static int recursiveTriangular(int n) {
      if (n == 1)
     return 1;  
      return recursiveTriangular(n-1) + n; 
   }

   public static void main(String args[]) {
      Scanner stdin = new Scanner(System.in);
      System.out.print("Please enter a number: ");
      int n = stdin.nextInt();
      System.out.println("The " + n + "-th triangular number is: " + 
                             recursiveTriangular(n)); 
   }
}

1

答えのほとんどは、iterative=であることを前提としているようですfor loop。あなたのforループが制限されていない場合は(ラ・ Cを、あなたはあなたのループカウンタでやりたいことができます)、それは正しいです。それが実際の forループである場合(たとえば、Pythonまたはループカウンターを手動で変更できないほとんどの関数型言語の場合)、それは正しくありません

すべての(計算可能な)関数は、再帰的に実装することも、whileループ(または条件付きジャンプ、基本的に同じもの)を使用して実装することもできます。本当に自分をfor loopsに制限すると、それらの関数のサブセットのみが取得されます(基本的な操作が妥当な場合は、プリミティブな再帰関数です)。確かに、これはかなり大きなサブセットであり、実際に使用する可能性のあるすべての機能が含まれています。

さらに重要なことは、多くの関数は再帰的に実装するのが非常に簡単で、反復的に実装するのが非常に難しいことです(手動でのコールスタックの管理はカウントされません)。


1

はい、タナクロン・タンダヴァスが言っように

再帰は、分割統治法で解決できる問題を解決する場合に適しています。

例:ハノイの塔

  1. サイズが大きくなるNリング
  2. 3極
  3. リングはポール1にスタックされます。ゴールは、リングをポール3にスタックされるように移動することです...しかし、
    • 一度に1つのリングしか移動できません。
    • 小さいリングの上に大きいリングを置くことはできません。
  4. 反復的な解決策は「強力でありながら醜い」ものです。再帰的なソリューションは「エレガント」です。

興味深い例。MC Erの論文「ハノイの塔と2進数字」はご存じでしょう。3brown1blueによる素晴らしいビデオでも扱われました。
Andrestand

0

私のコンピューターサイエンスの教授が、再帰的な解決策を持つすべての問題には反復的な解決策もあると言ったのを覚えているようです。彼は、再帰的ソリューションは通常遅いと言いますが、反復的ソリューションよりも推論とコーディングが容易な場合に頻繁に使用されます。

ただし、より高度な再帰的ソリューションの場合、単純なforループを使用して常にそれらを実装できるとは思いません。


再帰アルゴリズムを(スタックを使用して)反復アルゴリズムに変換することは常に可能です。あなたは特に単純なループに終わらないかもしれませんが、それは可能です。
Bernhard Barker

-4

再帰+暗記は、純粋な反復アプローチと比較してより効率的なソリューションにつながる可能性があります。たとえば、これを確認してください:http : //jsperf.com/fibonacci-memoized-vs-iterative-for-large-n


3
再帰的なコードは、スタックを使用して機能的に同一の反復コードに変換できます。表示されている違いは、同じ問題を解決するための2つのアプローチの違いであり、再帰と反復の違いではありません。
Bernhard Barker

-6

短い答え:トレードオフは、再帰が高速であり、forループがほとんどすべての場合に使用するメモリが少ないことです。ただし、通常はforループまたは再帰を変更して実行を高速化する方法があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.