質問

レディットのスレッド 明らかに興味深い質問を提起しました:

末尾再帰関数は簡単に反復関数に変換できます。その他のものは、明示的なスタックを使用して変換できます。できる 再帰は反復に変換されますか?

この投稿の (反?) 例は次のペアです。

(define (num-ways x y)
  (case ((= x 0) 1)
        ((= y 0) 1)
        (num-ways2 x y) ))

(define (num-ways2 x y)
  (+ (num-ways (- x 1) y)
     (num-ways x (- y 1))
役に立ちましたか?

解決

あなたは常に反復一つに再帰関数を回すことができますか?メモリがあります場合は、[はい、絶対に、そしてチャーチ=チューリングのテーゼは、それを証明しています。レイに関しては、それがどのような再帰関数によって計算可能であることは、反復(例えばチューリングマシンなどの)モデルとその逆によって計算可能であることを述べています。論文は、変換を行うには、正確にどのように伝えていないが、それはそれは間違いなく可能だと言うんます。

は、多くの場合、再帰関数を変換することは容易です。クヌースは「コンピュータプログラミングの芸術」のいくつかの技術を提供しています。そして、多くの場合、再帰的に計算されたものは少なく、時間と空間に完全に異なるアプローチによって計算することができます。この古典的な例は、フィボナッチ数や配列です。あなたの学位の計画では、この問題を確実に会った。

このコインのフリップ側では、我々は確かので、正確にコンピュータを伝えるの手間をかけずに高速の利益を提供し、前の結果をmemoizeへの招待として、式の再帰的定義を治療するように高度なプログラミングシステムを想像することができますこれは再帰的定義を有する式の計算に従う手順。ダイクストラは、ほぼ確実にこのようなシステムを想像しました。彼は、プログラミング言語の意味論からの実装を分離しようとして長い時間を過ごしました。その後、再び、彼の非決定論とマルチプロセッシングのプログラミング言語は、練習のプロのプログラマー上記のリーグである。

最終的な分析では、多くの機能は、理解して読んで、再帰形で書くことが単なる簡単です。説得力のある理由がありますない限り、あなたはおそらく(手動)これらの関数は、明示的に反復アルゴリズムに変換するべきではありません。お使いのコンピュータが適切にそのジョブを処理します。

私は1つの説得力のある理由を見ることができます。あなたは[の着用アスベスト下着のスキーム、Lispの、ハスケル、OCamlで、Perlの、またはパスカルのような超高レベル言語でプロトタイプシステムをしたとします。条件はあなたがCやJavaでの実装を必要とするようなものであると仮定します。 (多分それの政治。)次に、あなたは確かにいくつかの関数を再帰的に書かれている可能性がありますが、あなたのランタイムシステムを爆発と思われる、文字通り翻訳します、。例えば、無限末尾再帰は、Schemeで可能ですが、同じイディオムは、既存のC環境のために問題が発生します。別の例では、パスカルでサポートが、Cがない字句ネストされた関数と静的スコープの使用である。

このような状況で、あなたは、元の言語に政治的な抵抗を克服しようとするかもしれません。あなたは、グリーンスパンの(冗談)第十法のように、Lispのひどく再実装する自分自身を見つけるかもしれません。それとも、ただソリューションとは全く異なるアプローチを見つけるかもしれません。しかし、いずれにしても、確実な方法があります。

他のヒント

すべての再帰関数に対して非再帰形式を記述することは常に可能ですか?

はい。簡単な形式的な証明は、両方のことを示すことです。 μ 再帰 GOTO などの非再帰微積分はどちらもチューリング完全です。すべてのチューリング完全計算は表現力において厳密に同等であるため、すべての再帰関数は非再帰チューリング完全計算によって実装できます。

残念ながら、GOTO の適切な正式な定義をオンラインで見つけることができないので、ここにその定義を示します。

GOTO プログラムは一連のコマンドです P で実行される レジスター機 そのような P 次のいずれかです。

  • HALT, 、実行を停止します
  • r = r + 1 どこ r 任意のレジスタです
  • r = r – 1 どこ r 任意のレジスタです
  • GOTO バツ どこ バツ ラベルです
  • IF r ≠ 0 GOTO バツ どこ r は任意のレジスタであり、 バツ ラベルです
  • ラベルの後に上記のコマンドのいずれかを続けます。

ただし、再帰関数と非再帰関数間の変換は、必ずしも簡単なわけではありません (コール スタックを無意識に手動で再実装する場合を除く)。

詳細については、を参照してください。 この答え.

再帰実際インタープリタまたはコンパイラにスタックまたは類似の構築物として実装されます。それはそれは常に(自動の場合)を行うの方法ですので、だからあなたは確かに、反復相手方のへの再帰関数に変換することができます。あなただけのアドホックで、おそらく非常に醜いと非効率的な方法で、コンパイラの仕事を複製することでしょう。

基本的に、はい、あなたは「前回のコールが」まで得ていた場所を覚えておくことがプッシュ明示的なスタックに(暗黙的にスタックに状態をプッシュ)メソッドの呼び出しを置き換えるされませんし、次に実行する必要が終わる何本質的には "代わりの方法」と呼ばれます。

私は、ループ、スタックとステートマシンの組み合わせは、基本的にメソッド呼び出しをシミュレートすることにより、すべてのシナリオのために使用することができることを想像します。これは(どちらかより速く、より効率的ないくつかの意味で)「より良い」であることを行っているかどうかは、一般的に言って、実際には不可能である。

  • 再帰関数の実行フローはツリーとして表現できます。

  • 同じロジックをループによって実行することもできます。ループでは、データ構造を使用してそのツリーを走査します。

  • 深さ優先のトラバーサルはスタックを使用して実行でき、幅優先のトラバーサルはキューを使用して実行できます。

したがって、答えは次のとおりです。はい。なぜ: https://stackoverflow.com/a/531721/2128327.

単一ループで再帰を実行できますか?はい、なぜなら

チューリング マシンは、単一のループを実行することによってすべての処理を実行します。

  1. 命令を取得し、
  2. それを評価し、
  3. 1に進みます。

はい、それは非再帰的なバージョンを記述することは常に可能です。自明な解決策は、スタックデータ構造を使用して、再帰的実行をシミュレートすることである。

はい、(しかし、再帰がはるかに楽しい、私見を読み取ることで)明示的にスタックを使用します。

原理的には、再帰を削除し、データ構造にし、コールスタックの両方に無限の状態を持っている言語での反復と交換することが常に可能です。これは、チャーチ=チューリングのテーゼの基本的な結果である。

実際のプログラミング言語を考えると、答えはとして明らかにされていません。問題は、プログラムに割り当て可能なメモリの量が限られているが、使用することができる呼び出しスタックの量が無制限(32ビットCスタック変数のアドレスである言語を有することが全く可能であるということですアクセスできません)。この場合、再帰は、それが使用することができ、より多くのメモリを持っているという理由だけで、より強力です。コールスタックをエミュレートするのに十分な明示的に割り当て可能なメモリがありません。これに関する詳細な議論については、このsemantics?">

時々交換する再帰はそれよりもはるかに簡単です。再帰は、1990年代にCSで教えられてファッショナブルなものにするために使用し、その当時からの平均の開発者の多くは、あなたが再帰で何かを解決した場合、それはよりよい解決策だった考え出しました。そこで彼らは、再帰の代わりに、順序を逆に後方ループ、またはそのような愚かなものを使用します。だから、時々、再帰を削除すると、運動の種類、簡単な「当たり前、それは明らかだった」されます。

ファッションは、他の技術にシフトしているように、

これは、今、問題の少ないます。

すべての計算機能はチューリングマシンで計算され、したがって、再帰的システムとチューリングマシン(反復システム)が同等であることができる。

アパート明示スタックから、反復に再帰を変換するための別のパターンは、トランポリンの使用である。

ここで、関数は、最終的な結果を返す、またはそれ以外行ったであろうことを、関数呼び出しの閉鎖のいずれか。その後、開始(トランポリン)関数は、クロージャを呼び出す保つ最終的な結果に到達するまで返されます。

このアプローチは、相互再帰関数のために動作しますが、私はそれが唯一の末尾呼び出しのために働くんです。

http://en.wikipedia.org/wiki/Trampoline_(computers)

関数呼び出しは後藤と(大まかに)スタック操作以外の何ものでもありません -

私はそう言うと思います。あなたがやらなければならないことは、(明示的にあまりにもこのキーワードを持っていない言語でのgotoを真似て)関数の呼び出し中に組み込まれているスタックを模倣し、後藤と同様の何かをされています。

ウィキペディアの次のエントリを参照してください。質問に対する完全な答えを見つける出発点として使用できます。

どこから始めればよいかについてのヒントが得られる段落の後に続きます。

漸化式を解くということは、 閉じた形式のソリューション:n の非再帰関数。

の最後の段落もご覧ください このエントリ.

  

非再帰的にすべての再帰的なアルゴリズムを変換することが可能です   1が、多くの場合、ロジックは、はるかに複雑で、そうすることが必要です   スタックを使用します。実際には、再帰自体は、スタックを使用しています。   機能スタックます。

詳細: https://developer.mozilla.org / EN-US /ドキュメント/ウェブ/ JavaScriptを/ガイド/機能する

tazzego、再帰関数は、あなたがそれを好きかどうか自分自身を呼び出すことを意味します。人々は物事は再帰なしで行うことができるかどうかについて話しているとき、彼らはこのことを意味し、有効な文として「私は再帰の定義と一致していないため、いや、それは、真実ではない」と言うことはできません。

を念頭に置いて、あなたがナンセンスであると言うだけで他のすべてについて。あなたはそれがナンセンスではないと言うだけで、他の事はあなたがコールスタックせずにプログラミングを想像することはできませんという考えです。これは、人気となったコールスタックを使用してまで、何十年も行われていたものです。 FORTRANの古いバージョンでは、コールスタックを欠いていたし、彼らはうまく働いています。

ところで、ループの手段としてのみ再帰を実装チューリング完全な言語(例えば、SML)が存在します。また、唯一の(例えば、FORTRAN IV)をループする手段として反復実施チューリング完全な言語が存在します。チャーチ=チューリングのテーゼは、再帰専用の言語で可能なものは、彼らの両方がチューリング完全性の性質を持っているという事実によって非再帰言語とヴィーカ、その逆で行うことができることを証明します。

ここでは、反復アルゴリズムであります:

def howmany(x,y)
  a = {}
  for n in (0..x+y)
    for m in (0..n)
      a[[m,n-m]] = if m==0 or n-m==0 then 1 else a[[m-1,n-m]] + a[[m,n-m-1]] end
    end
  end
  return a[[x,y]]
end
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top