web-dev-qa-db-ja.com

反復よりも再帰を使用する利点はありますか-時々読みやすさと優雅さ以外に?

私は2つの仮定をしようとしています。間違っている場合は修正してください:

  1. 同等の反復なしの再帰的アルゴリズムはありません。
  2. 反復は、再帰よりも常にパフォーマンス面で安価です(少なくとも、Java、C++、Pythonなどの汎用言語では)。

再帰は常に反復よりもコストがかかり、反復アルゴリズム(それを許可する言語では)で常に置き換えることができるというのが本当である場合-再帰を使用する残りの2つの理由は、エレガントさと読みやすさです。

一部のアルゴリズムは、再帰によってよりエレガントに表現されます。例えば。バイナリツリーをスキャンします。

しかし、それとは別に、反復よりも再帰を使用する理由はありますか?再帰には、時々優雅さと読みやすさ以外に、反復よりも利点がありますか?

8
Aviv Cohn

まあ、それは通常より少ないコードです。

また、コードが少ないほど、エラーが発生しにくくなります。

特に、反復ソリューションでスタックを使用して再帰をシミュレートする必要がある場合、再帰は非常に有益です。再帰は、コンパイラがすでにスタックを管理していて、必要なことを正確に実行することを認めています。独自の管理を開始すると、回避したい関数呼び出しのオーバーヘッドが再導入されるだけでなく、しかし、かなりバグのない形式ですでに存在するホイール(バグの余地が十分にある)を再発明しています。

IMO、スタックなしで自然かつ簡単に実行できる場合にのみ、再帰を避けます。 (または他の非スカラー状態および/またはスコープ管理構造。)

12
svidgen

第一に、再帰的アルゴリズムと同等の反復等価物が常に存在することは事実ですが、「より良い」の合理的な定義については、反復等価物がより優れているとは限りません。

一部のアルゴリズムでは、同等の反復は再帰アルゴリズムをシミュレートするだけで終了し、シミュレートされたパラメーターとローカル変数のスタックおよびアンスタックが完了します。 Ackermannの関数 を考えます。 ハフマンコーディング を検討してください。これらの場合、明示的なスタック操作を(再)書き込むことによって得られることはほとんどありません。

第2に、再帰が常に反復よりも高価であるとは限りません。 tail recursion を検討し、「Lambda:The Ultimate ...」の論文を読んでください。

(はい、これは拡張が必要であることを知っています。今すぐ医師の診察に行かなければなりません。)

はい、言えることはいくつかあります。

Tony Hoare氏は当初、Quicksortを繰り返し説明しており、説明するのは非常に難しいと報告しました。再帰的に説明すると、それは単純です。詳細は HaskellのQuicksort を参照してください。配列の長さが1に等しい場合は終了です。それ以外の場合は、実際に並べ替える必要があります。まず、ピボット要素を選択します。従来、これは最初の要素ですが、任意の要素を使用できます。次に、配列にパスを渡して、3つのサブ配列を形成します。 1つ目はすべての要素がピボットよりも小さい要素、2つ目がすべての要素がピボットに等しい要素、3つ目がすべての要素がピボットよりも大きい要素です。 (配列要素の値が一意である必要がある場合、2番目のサブ配列の長さは1に等しくなります。)ここで、最初のサブ配列をクイックソートし、次に2番目のサブ配列をクイックソートします。

二分探索は、再帰的に提示すると理解しやすく、実際、末尾再帰です。配列の中央の要素を調べます。それが探している値と等しい場合は、完了です。中央の要素が求める値よりも大きい場合、目的の値は「左側」にある必要があることがわかっているため、中央の要素の前にサブ配列を検索します。それ以外の場合は、中央の要素の後のサブ配列を検索します。どちらの場合も、中央の要素はターゲットではないことがわかっているため、省略します。ターゲットを見つけるか、検索する配列が不足しています。その時点で、あなたはずっと保釈し、あなたは終わった。 2014年、2019年は気にしないでください。自尊心のあるコンパイラーは、Javaコンパイラーであっても、末尾再帰の最適化を行う方法を知っています。(注:従来のJava仮想マシンは、一般的なGOTO操作がないため、一般的な末尾呼び出しの最適化をサポートしていませんが、末尾再帰の最適化には影響しません。)

多くの場所での実際の問題は、ショーを実行している人々が末尾再帰の最適化について学んだことがないため、すべての再帰を禁止することです。私はNortel Networksでこれに遭遇し、それを説明するコメントの全ページといくつかの関連する概念[〜#〜]と[〜#〜]にアセンブリを示す必要がありましたコンパイラが実際に再帰呼び出しを生成していないことを証明する言語リスト。 Nortelはなくなったが、それらのマネージャーはまだ多くの場所に存在している。

お役に立てれば。

4
John R. Strohm

再帰は、コールスタックを使用して、関数呼び出しの戻りを格納します。関数の状態は呼び出しの間に保存されます。

自分でスタックを作成する場合を除いて、反復ではスタックまたは同様のメカニズムを使用して中間状態を保存する必要もあります。もちろん、そのような状態の保存を必要としない代替アルゴリズムを見つけることができます。

スタックがオーバーフローした場合に限り、再帰はよりコストがかかります。ある意味では、再帰がすでに提供している状態ストレージメカニズムを再作成しているため、反復は(再帰に適したアルゴリズムでは)よりコストがかかります。

3
Robert Harvey