そうでない場合、再帰的な対応物が存在しない反復アルゴリズムを示す優れたカウンターの例はありますか?
すべての反復アルゴリズムを再帰的に表現できる場合、これを行うのがより難しいケースはありますか?
また、このすべてにおいてプログラミング言語はどのような役割を果たしますか? Schemeプログラマは、Javaのみのプログラマとは異なる反復(=末尾再帰)とスタックの使用方法を持っていると想像できます。
これには簡単なアドホック証明があります。厳密に反復的な構造を使用してチューリング完全言語と再帰構造のみを使用してターニング完全言語を構築できるため、この2つは同等です。
すべての反復アルゴリズムを再帰的に表現できますか?
はい、しかし証明は興味深いものではありません:
すべての制御フローを含むプログラムを、各分岐がbreak
、return
、exit
、 raise
など。 Caseステートメントが次に実行するブロックを決定するために使用する新しい変数(「プログラムカウンター」と呼びます)を導入します。
この構造は、さまざまな制御フロー構造の相対的な表現力について人々が主張していた1960年代の大規模な「構造化プログラミング戦争」の間に発見されました。
ループを再帰関数に置き換え、すべての可変ローカル変数をその関数のパラメーターに置き換えます。ほら!反復は再帰に置き換えられました。
この手順は、元の関数のインタープリターを作成することになります。ご想像のとおり、コードが読めなくなり、面白いことではありません。 ただし、一部の手法は、関数型言語でのプログラミングを初めて学ぶ、命令型プログラミングのバックグラウンドを持つ人に役立ちます。
あなたが言うように、すべての反復アプローチは「再帰的」アプローチに変えることができ、末尾呼び出しを使用すると、スタックも爆発しません。 :-)実際、Schemeは実際にループのすべての一般的な形式を実装しています。スキームの例:
(define (fib n)
(do ((x 0 y)
(y 1 (+ x y))
(i 1 (+ i 1)))
((> i n) x)))
ここでは、関数は反復的に見えますが、実際には、3つのパラメーターx
、y
、およびi
を受け取る内部ラムダで再帰し、それぞれで新しい値で自分自身を呼び出します反復。
関数をマクロ展開する方法の1つを次に示します。
(define (fib n)
(letrec ((inner (lambda (x y i)
(if (> i n) x
(inner y (+ x y) (+ i 1))))))
(inner 0 1 1)))
このようにして、再帰的な性質がより視覚的に明らかになります。
反復を次のように定義します:
function q(vars):
while X:
do Y
次のように翻訳できます:
function q(vars):
if X:
do Y
call q(vars)
ほとんどの場合、Yには、Xによってテストされるカウンターのインクリメントが含まれます。この変数は、再帰ルートを実行するときに、何らかの方法で 'vars'に渡す必要があります。
their answer の台座に示されているように、 recursion と反復が同等であり、同じ問題を解決するために両方を使用できることを示す証明を構築できます。ただし、2つが同等であることはわかっていますが、一方を他方に使用することには欠点があります。
再帰に最適化されていない言語では、反復を使用するアルゴリズムは再帰よりも高速にプリフォームすることがわかります。同様に、最適化された言語でも、異なる言語で記述された反復を使用するアルゴリズムは再帰よりも高速に実行されることがあります。さらに、再帰と反復を使用して、またはその逆を使用して、特定のアルゴリズムを記述する明確な方法がない場合があります。これにより、コードが読みにくくなり、保守性の問題が発生する可能性があります。