反復は再帰を置き換えることができますか?


42

私は、例えば、すべてのスタックオーバーフローの上で見てきたここではここではここではここではここで私は言及する気にしないいくつかの他、「用途再帰が唯一の反復を使用してプログラムに変換することができ、任意のプログラム」という。

はい、それは可能だと言った、非常に支持された答えを持つ、非常に支持されたスレッドさえありました。

今、私は彼らが間違っていると言っているのではありません。その答えは、コンピューティングについての私のわずかな知識と理解に反するだけです。

すべての反復関数は再帰として表現でき、ウィキペディアにはその旨の記述があると思います。しかし、その逆は真実ではないでしょう。1つには、非原始再帰関数を繰り返し表現できるかどうか疑問です。

また、ハイパーオペレーションを繰り返し表現できるかどうかも疑問です。

@YuvalFIlmusの質問に対する彼の答え(ちなみに私は理解できません)で、数学的操作のシーケンスを加算のシーケンスに変換することは不可能であると述べました。

YFの答えが確かに正しい場合(そうだと思いますが、彼の推論は私の頭の上にありました)、これはすべての再帰が反復に変換できるわけではないという意味ではありませんか?なぜなら、すべての再帰を反復に変換できれば、すべての操作を一連の加算として表現できるからです。

私の質問はこれです:

すべての再帰を反復に変換できますか?

明るい高校生または最初の1年生が理解できる答えを与えてください。ありがとうございました。

PS原始再帰とは何かわかりません(アッカーマン関数については知っていますし、原始再帰ではありませんが、まだ計算可能です。それに関する私の知識は、アッカーマン関数のウィキペディアのページから得られます)。

PPS:答えが「はい」の場合、たとえば、非原始再帰関数の反復バージョンを記述できますか。たとえば、答えはアッカーマンです。理解するのに役立ちます。


21
CPUで実行するものはすべて反復的です。高次言語で再帰的に記述することもできますが、いずれにしても、アセンブラーの反復命令の束にコンパイルされます。まさに文字通り、コンパイラーはあなたが求めていることを正確に行い、すべての空想的な再帰をCPUのための一連の反復命令に変換します。
Davor

6
低レベルでは、ほとんどの人は再帰が反復とスタックに等しいことを知っています。コンテキストフリー文法は再帰をモデル化しますが、プッシュダウンオートマトンはスタックメモリを使用した反復的な「プログラム」です。
ヘンドリック

2
一般に、他の回答が表示されるかどうかを確認するために少なくとも24時間待つことをお勧めします。受け入れられた質問は非常に長すぎて率直に言って複雑で、必要以上に意図的に問題を複雑にしているようです。あなたの質問に対する基本的な答えは、「再帰に使用されるスタックは明示的に反復的に実装できる」というものであり、それよりもはるかに複雑である必要はありません。とにかく、再帰スタックにもその機能が必要であるため、メモリが制限されていないかどうかに関する考慮事項は機能しません。
AnoE

反復のみでチューリングマシンエミュレータを実装することができます
Sarge Borsch

私がかなり前に取った比較言語のクラスでは、アセンマンの関数をアセンブリ、FORTRAN(現代のFortranではない)、および1つの選択の再帰言語で記述しなければなりませんでした。はい、実際には可能です。そしてもちろん、理論的には可能です。たとえば、チューリングマシンとラムダ計算が同等であることを証明する質問を参照してください。
デビッドハメン

回答:


52

反復と無制限のメモリによって再帰を置き換えることができます

繰り返し(たとえば、whileループ)と有限量のメモリしかない場合は、有限オートマトンしかありません。メモリの量が限られている場合、計算には可能なステップの数が有限であるため、有限オートマトンですべてをシミュレートすることができます。

無制限のメモリがあると、取引が変わります。この無制限のメモリは、同等の表現力を持つことが判明した多くの形をとることができます。たとえば、チューリングマシンはシンプルです。1本のテープがあり、コンピューターは一度に1ステップずつテープ上を前後にしか移動できませんが、再帰関数でできることは何でもできます。

チューリングマシンは、必要に応じて成長する追加のストレージを備えたコンピューター(有限状態マシン)の理想的なモデルと見なすことができます。テープに有限の境界が存在するだけでなく、入力が与えられたとしても、必要なテープの量を確実に予測できないことが重要です。入力から必要なテープの量を予測(計算)できる場合は、最大テープサイズを計算し、現在の有限テープを含むシステム全体を有限状態マシンとして扱うことにより、計算を停止するかどうかを決定できます。 。

コンピューターでチューリングマシンをシミュレートする別の方法は次のとおりです。テープの先頭をメモリに保存するコンピュータープログラムでチューリングマシンをシミュレートします。計算がメモリに収まるテープの部分の終わりに達した場合、コンピューターをより大きなコンピューターに交換し、計算を再実行します。

ここで、コンピューターを使用して再帰的計算をシミュレートするとします。再帰関数を実行する手法はよく知られています。各関数呼び出しにスタックフレームと呼ばれるメモリがあります。重要なのは、再帰関数が変数を渡すことで複数の呼び出しを通じて情報を伝播できることです。コンピューターでの実装という点では、関数呼び出しは(grand-)*親呼び出しのスタックフレームにアクセスする可能性があることを意味します。

コンピューターはプロセッサーであり、有限状態マシン(膨大な数の状態がありますが、ここでは計算理論を行っているため、重要なのは有限であるということです)と有限メモリーを組み合わせたものです。マイクロプロセッサは1つの巨大なwhileループを実行します。「電源がオンになっている間に、メモリから命令を読み取り、実行します」。(実際のプロセッサはそれよりもはるかに複雑ですが、計算できるものには影響せず、どれだけ速く便利に実行できます。)コンピューターは、このwhileループだけで再帰関数を実行して、反復とメカニズムを提供できます。自由にメモリのサイズを増やす機能を含むメモリにアクセスします。

あなたは原始再帰に再帰を制限する場合は、に反復を制限することができ有界反復。つまり、予測不可能な実行時間でwhileループを使用する代わりに、ループの先頭で反復回数がわかっているループに使用できます¹。反復の数は、プログラムの開始時にはわからない場合があります。それ自体は、以前のループによって計算された可能性があります。

ここでは証明をスケッチするつもりはありませんが、プリミティブな再帰から完全な再帰への移行とforループからwhileループへの移行との間には直感的な関係があります。どちらの場合も、やめる。完全再帰では、最小化演算子を使用してこれを行います。最小化演算子では、条件を満たすパラメーターが見つかるまで続けます。whileループの場合、これはループ条件が満たされるまで継続することで行われます。

¹Cに似た言語のループは、のように無制限の反復を実行できます。これは、ループを有界の反復に制限するのが慣例です。計算理論で人々が「forループ」について話すとき、それは1から(または同等)までカウントするループのみを意味します forwhilen


要求されたレベルに維持された詳細な説明を受け入れます。
トビアラフィン16

12
実際のコンピューターよりも注目に値する価値があると思います。再帰もメモリを消費します(呼び出しスタックの増加)。したがって、実際のアプリケーションでは、無制限のメモリは必要ありません(すべてが同じように制限されているため)。そして、再帰はしばしば反復よりも多くのメモリを必要とします。
Agent_L 16

@Agent_Lはい、すべてのスタックフレームを保存するには、再帰に無制限のメモリが必要です。再帰アプローチでは、無制限のメモリが必要ですが、反復などの操作のシーケンスから直感的に分離することはできません。
ジル「SO-悪であるのをやめる」

1
おそらく興味深いのは、教会とチューリングの論文でしょう。チューリングマシンは、再帰の(固有の)概念を持たない非常に反復的なマシンです。ラムダ計算は、計算を再帰的に表現するために考案された言語です。Church-Turing論文は、すべてのラムダ式がチューリングマシンで評価され、再帰と反復をリンクできることを示しました。
コートアンモン

1
@BlackVegetable再帰メソッドに内部変数がなく、使用するメモリがコールスタック(TCOによって最適化できるもの)のみである場合、反復バージョンは内部変数も持たず、一定量のメモリのみを使用します(変数)カウンターなど、すべての反復で共有されます。
Agent_L 16

33

CPUは、フェッチと実行の無限反復を使用して任意のプログラムを実行するため、すべての再帰を反復に変換できます。これはベーム・ヤコピニの定理の形式です。さらに、チューリング機械カウンター機械など、多くのチューリング完全な計算モデルには再帰がありません。

プリミティブな再帰関数は、有界反復を使用するプログラムに対応しています。つまり、ループが事前に実行される反復回数を指定する必要があります。アッカーマン関数はプリミティブな再帰的ではないため、有界反復は一般に再帰をシミュレートできません。ただし、無制限の反復は、部分的に計算可能な関数をシミュレートできます。


25

Gillesからの回答の例として、ここにAckermann関数の「反復」アルゴリズムがあります(Wikipedia言及され ている一般的なAckermann-Péterバージョンを使用)。a(n,m)

私たちは、スタックが必要整数で。s

このようなスタックには、(スタックに新しい要素を配置する)と上の要素を取得(および削除)し、1つのクエリ操作。スタックに要素が残っていない場合はtrueを返します(さらに要素がある場合はfalse)。 。push(s,x)xxpop(s)empty(s)

Ackermann(n0,m0):

  • s= (空のスタックを初期化)
  • push(s,n0)
  • push(s,m0)
  • while(true):
    • mpop(s)
    • if(empty(s)):
      • return m (これでループが終了します)
    • npop(s)
    • if(n=0):
      • push(s,m+1)
    • else if(m=0):
      • push(s,n1)
      • push(s,1)
    • else:
      • push(s,n1)
      • push(s,n)
      • push(s,m1)

これをCeylonで実装しました。必要に応じてWebIDE実行できます。(ループの各反復の開始時にスタックを出力します。)

もちろん、これは暗黙的な呼び出しスタックを再帰から、パラメーターと戻り値を持つ明示的なスタックに移動しただけです。


15
これは重要なポイントだと思います。再帰は特別なものではなく、コンパイラに実行させるのではなく、自分で呼び出しスタックを追跡するだけで削除できることを明確に示しました。再帰は、この種のプログラムを簡単に作成できるコンパイラ機能です。
デビッドリチャービー16

4
要求されたプログラムを書く努力をしていただきありがとうございます。
トビアラフィン16

16

すでにいくつかの素晴らしい答えがあります(私は競争することさえ望みません)が、この簡単な説明を売り込みたいです。

再帰は、ランタイムスタックの操作です。再帰は、新しいスタックフレームを追加し(再帰関数の新しい呼び出し用)、戻るとスタックフレームを削除します(再帰関数の完成した革新のため)。再帰により、いくつかのスタックフレームが追加/削除され、最終的にすべてが終了し(うまくいけば!)、結果が呼び出し元に返されます。

さて、独自のスタックを作成し、再帰関数呼び出しをスタックへのプッシュに置き換え、再帰関数からの戻りをスタックのポップに置き換え、スタックが空になるまで実行されるwhileループがあった場合はどうなりますか?


2

私の知る限り、そして私自身の経験では、反復として反復を実装できます。上記のように、再帰はスタックを使用します。これは概念的には無制限ですが、実際には制限されています(スタックオーバーフローメッセージを受け取ったことはありますか?)。私のプログラミングの初期(最後の千年紀の前世紀の第3四半期)に、再帰アルゴリズムを実装する非再帰言語を使用していましたが、問題はありませんでした。しかし、どのように証明するのかはわかりません。

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