末尾再帰と構造再帰の違い


8

構造再帰とテール再帰の間に違いはありますか、それとも両方同じですか?これらの再帰の両方で、再帰関数が元のアイテムのサブセットで呼び出されていることがわかります。


7
どのような研究をしましたか?末尾再帰についてどう理解していますか?なぜ構造的再帰に似ていると思いますか?
ジョナサンキャスト

あなたが見たものを追加して、それらが同じまたは非常に似ていると思わせる可能性はありますか?概念を人々に教えるときに、インストラクターがそれを明確にするのに役立つかもしれません。
user541686

回答:


28
  1. 構造的な再帰:再帰的な呼び出しは、構造的に小さい引数に対して行われます。

  2. 末尾再帰:再帰呼び出しは最後に発生します。

末尾の再帰をより小さな引数で呼び出す必要があるという要件はありません。実際、末尾再帰関数は、多くの場合、永久にループするように設計されています。たとえば、これはささいな末尾再帰です(あまり役に立ちませんが、末尾再帰です)。

def f(x):
   return f(x+1)

実際にはもう少し注意する必要があります。関数にはいくつかの再帰呼び出しがあり、それらすべてが末尾再帰である必要はありません。

def g(x):
  if x < 0:
    return 42             # no recursive call
  elif x < 20:
     return 2 + g(x - 2)  # not tail recursive (must add 2 after the call)
  else:
     return g(x - 3)     # tail recursive

末尾再帰呼び出しについて話します。再帰呼び出しがすべて末尾再帰である関数は、末尾再帰関数と呼ばれます。



コメントは詳細な議論のためのものではありません。この会話はチャットに移動しました
DW

-1

末尾再帰は、構造再帰の非常に単純なケースであり、問​​題の構造はリンクリストです。主に使用している言語では、このリストは文字通りコードに含まれていない可能性があります。むしろ、それは概念的な「関数の呼び出しのリスト」であり、その言語を使用して書かれたように表現することができない可能性がある概念です。Haskell(私の言語)では、末尾再帰関数呼び出しは、実際には、要素が文字どおり「関数の呼び出し」であるリテラルリストのシーケンスアクションで置き換えることができますが、これはおそらく関数型言語のものです。

構造的再帰は、他の(場合によっては複合)オブジェクトの複合として定義されたオブジェクトを操作する方法です。たとえば、バイナリツリーは2つのバイナリツリーへの参照を含むオブジェクトであるか、空です(したがって、再帰的に定義されたオブジェクトです)。自己参照的ではありませんが、t1とt2がペアである必要はありませんが、いくつかのタイプt1とt2の2つの値を含むペア(t1、t2)は構造的再帰を許可します。この再帰は次の形式を取ります

ペアのアクション=各要素の他のアクションの結果の組み合わせ

あまり深遠に聞こえません。

構造的な再帰末尾再帰にならないことがよくあります、あらゆる種類の再帰は末尾再帰として書き直すことができます(証明:元の再帰を実行すると、アクションは特定の順序で完了します。したがって、再帰これは、前に説明したように、末尾再帰である特定の一連のアクションを実行することと同じです)。

バイナリツリーまたは上記のペアの例のいずれかがこれを示しています。ただし、サブオブジェクトに再帰呼び出しを配置する場合、最後のアクションにできるのはそのうちの1つだけです。結果が何らかの方法(たとえば、加算)で結合されている場合、どちらもない可能性があります。Andrej Bauerが彼の回答で述べているように、これは、結果が変更されている限り、再帰呼び出しが1つだけでも発生する可能性があります。言い換えると、効果的にリンクされたリスト以外のすべてのタイプのオブジェクト(1つのサブオブジェクトだけが下にある)の場合、構造再帰は末尾再帰ではありません。


1
末尾再帰が想像上のまたは実際のリストのみに関するものであることは誤りです。たとえば、二分木に対してテール再帰を行うことは完全に可能です。リストの残りの部分がその「テール」であるため、誰かがそれをだと思う理由がわかります。
Andrej Bauer

@AndrejBauer私は何が悪いのかを正確に理解しているとき、私はこれについて適切に恥ずかしく感じるでしょう。フォームの末尾再帰は、(Haskell状態モナドスタイルで)のf x = (stuff defining x'); f x'ように定義されたリンクリスト内のノードのシーケンスと同じであることはトートロジーのようl = modify f : lです。私にとっては、用語の類似性だけではありませんでした。二分木に対する末尾再帰について、詳しく説明していただけませんか?線形化の事実については、最後から2番目の段落からしか考えることができません。
ライアンライヒ

すべての末尾再帰呼び出しがその形式であるわけではありません。たとえば、(caseステートメントなどの)異なるブランチに複数の末尾再帰呼び出しがある場合があります。それらをツリーのパスを選択することと考えるのが自然です。また、呼び出しは末尾再帰(または末尾再帰ではない)であるためf (f x)、の外部呼び出しが末尾再帰である場合があることに注意してくださいf。それはリストに関するものだという見方にどのように適合しますか?:ここでは別の例ですf : (Int -> Int) -> (Int -> Int)f g 0 = g 42してf g (n + 1) = f (f . g) n。可能性は無限であり、いくつかは有用です。
Andrej Bauer

@AndrejBauer問題は単に末尾呼び出しではなく末尾再帰に関するものだったので、私は該当するものとは考えません。外側のfの評価では、内側のものが末尾呼び出しではありません(fがIDでない限り)。ifステートメントは、末尾呼び出しで分岐しないように簡単に書き換えることができます。タイプチェックを行わないため、最後の例は無効です。それでも、それはまだ末尾再帰ではありません:への呼び出しではなく、純粋に異なるラムダです。(次へ)f (f x)if (c) then f a else f b == let x = if (c) then a else b in f xf . gf g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)f
ライアンライヒ2017

実際には、例は相互の末尾再帰であると言います。つまりf g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }、それを議論に取り入れれば、再帰関数であるかどうかにかかわらず、末尾が許容され、この用語は無意味になります。
ライアンライヒ2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.