web-dev-qa-db-ja.com

再帰と反復

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

そして、再帰をforループに常に変換できる場合、経験則はありますか?

92
Breako Breako

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

末尾呼び出しの最適化 などの一部の最適化は、再帰を高速化しますが、常に可能であるとは限らず、すべての言語で実装されているわけではありません。

再帰を使用する主な理由は次のとおりです。

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

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

132
Denys Séguret

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

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

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

「通常は遅い」わけではありません。間違って適用される再帰が遅いのです。それに加えて、最新のコンパイラーは、問い合わせをせずに一部の再帰をループに変換するのに優れています。

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

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

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


+
49
dasblinkenlight

質問:

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

回答:

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

試してみるのも良いことです:Merge sortを繰り返し書きます。かなり時間がかかります。

質問:

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

回答:

はい。この thread には、これに対する非常に良い答えがあります。

質問:

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

回答:

私を信じて。独自のバージョンを作成して、深さ優先の検索を反復的に解決してください。再帰的に解決する方が簡単な問題があることに気付くでしょう。

ヒント:再帰は、 divide and conquer テクニックで解決できる問題を解決する場合に適しています。

24

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

4
G. Steigert

反復を使用して同等のメソッドを記述するには、スタックを明示的に使用する必要があります。反復バージョンがその解決のためにスタックを必要とするという事実は、問題が再帰から利益を得ることができるほど難しいことを示しています。原則として、再帰は、一定量のメモリでは解決できない問題に最適であり、反復的に解決する場合はスタックが必要です。そうは言っても、再帰と反復は異なるパターンに従う間、同じ結果を示す可能性があります。

たとえば、三角形シーケンスの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)); 
   }
}
3
shirin

ほとんどの答えは、iterative = for loopと仮定しているようです。 forループが制限されていない場合(a laC、ループカウンターで何でもできます)、それは正しいです。 realforループの場合(たとえば、Pythonまたは手動でループを変更できないほとんどの機能言語の場合)カウンター)、それはnot正しいです。

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

さらに重要なことは、多くの関数は再帰的に実装するのが非常に簡単で、繰り返し実装するのは非常に難しいことです(呼び出しスタックを手動で管理することは重要ではありません)。

1
Jbeuh

はい、 said by Thanakron Tandavas

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

例:ハノイの塔

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

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

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