Unterschied zwischen Schwanzrekursion und struktureller Rekursion
8
Gibt es einen Unterschied zwischen Strukturrekursion und Schwanzrekursion oder sind beide gleich? Ich sehe, dass in diesen beiden Rekursionen die rekursive Funktion für die Teilmenge der ursprünglichen Elemente aufgerufen wird.
Welche Recherchen haben Sie durchgeführt? Was verstehen Sie unter Schwanzrekursion? Warum ähnelt es Ihrer Meinung nach der strukturellen Rekursion?
Jonathan Cast
Gibt es eine Chance, dass Sie hinzufügen, was Sie gesehen haben, was Sie glauben ließ, dass sie gleich oder sehr ähnlich sind? Dies kann den Ausbildern helfen, dies zu klären, wenn sie den Menschen die Konzepte beibringen.
user541686
Antworten:
28
Strukturelle Rekursion: Rekursive Aufrufe werden für strukturell kleinere Argumente durchgeführt.
Schwanzrekursion: Der rekursive Aufruf ist das Letzte, was passiert.
Es ist nicht erforderlich, dass die Schwanzrekursion für ein kleineres Argument aufgerufen wird. Tatsächlich sind Schwanzrekursionsfunktionen häufig so konzipiert, dass sie für immer eine Schleife bilden. Hier ist zum Beispiel eine triviale Schwanzrekursion (nicht sehr nützlich, aber es ist eine Schwanzrekursion):
def f(x):
return f(x+1)
Wir müssen tatsächlich etwas vorsichtiger sein. Eine Funktion kann mehrere rekursive Aufrufe enthalten, und nicht alle müssen rekursiv sein:
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
Man spricht von rekursiven Schwanzaufrufen . Eine Funktion, deren rekursive Aufrufe alle schwanzrekursiv sind, wird dann als rekursive Schwanzfunktion bezeichnet.
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
DW
-1
Die Schwanzrekursion ist ein sehr einfacher Fall der strukturellen Rekursion, bei der es sich bei der fraglichen Struktur um eine verknüpfte Liste handelt . In der Sprache, die Sie wahrscheinlich hauptsächlich verwenden, ist diese Liste wahrscheinlich nicht wörtlich im Code enthalten. Vielmehr handelt es sich um eine konzeptionelle "Liste von Aufrufen der Funktion", ein Konzept, das möglicherweise nicht so ausgedrückt werden kann, wie es in dieser Sprache geschrieben wurde. In Haskell (meiner Sprache) kann jeder schwanzrekursive Funktionsaufruf tatsächlich durch Sequenzierungsaktionen in einer Literalliste ersetzt werden, deren Elemente buchstäblich "Aufrufe einer Funktion" sind, aber dies ist wahrscheinlich eine funktionale Sprache.
Die strukturelle Rekursion ist eine Methode zum Bearbeiten eines Objekts, das als Verbund aus anderen (möglicherweise zusammengesetzten) Objekten definiert ist. Ein Binärbaum ist beispielsweise ein Objekt, das Verweise auf zwei Binärbäume enthält, oder ist leer (daher handelt es sich um ein rekursiv definiertes Objekt). Weniger selbstreferenziell lässt ein Paar (t1, t2), das zwei Werte einiger Typen t1 und t2 enthält, eine strukturelle Rekursion zu, obwohl t1 und t2 nicht auch Paare sein müssen. Diese Rekursion hat die Form
Aktion für das Paar = Kombination der Ergebnisse anderer Aktionen für jedes Element
das klingt nicht sehr tief.
Es ist häufig der Fall, dass eine strukturelle Rekursion nicht schwanzrekursiv sein kann , obwohl jede Art von Rekursion als Schwanzrekursion umgeschrieben werden kann (Beweis: Wenn Sie die ursprüngliche Rekursion ausführen, werden die Aktionen in einer bestimmten Reihenfolge ausgeführt, daher die Rekursion ist gleichbedeutend mit der Ausführung dieser bestimmten Abfolge von Aktionen, die, wie ich zuvor besprochen habe, eine Schwanzrekursion ist.
Entweder der Binärbaum oder das obige Paarbeispiel zeigen dies: Wenn Sie jedoch die rekursiven Aufrufe für die Unterobjekte anordnen, kann nur einer von ihnen die letzte Aktion sein. möglicherweise ist keiner von beiden, wenn ihre Ergebnisse auf irgendeine Weise kombiniert werden (z. B. Addition). Wie Andrej Bauer in seiner Antwort sagt, kann dies sogar mit nur einem rekursiven Aufruf geschehen, solange das Ergebnis geändert wird. Mit anderen Worten, für jeden Objekttyp außer denjenigen, die effektiv verknüpfte Listen sind (nur ein Unterobjekt ganz nach unten), ist strukturelle Rekursion keine Schwanzrekursion.
Es ist falsch, dass es bei der Schwanzrekursion nur um imaginäre oder reale Listen geht. Es ist beispielsweise durchaus möglich, eine Schwanzrekursion über Binärbäume durchzuführen. Ich kann sehen, warum jemand denken würde, dass es so ist, weil der Rest der Liste sein "Schwanz" ist.
Andrej Bauer
@AndrejBauer Ich werde mich darüber angemessen schämen, wenn ich genau verstehe, was los ist. Es scheint tautologisch, dass die Schwanzrekursion des Formulars f x = (stuff defining x'); f x'mit der Sequenzierung von Knoten in einer verknüpften Liste identisch ist, die wie folgt definiert ist l = modify f : l(im Haskell-State-Monad-Stil). Es war nicht nur die terminologische Ähnlichkeit für mich. Könnten Sie die Schwanzrekursion über binäre Bäume näher erläutern? Ich kann nur an die Linearisierung aus meinem vorletzten Absatz denken.
Ryan Reich
Nicht alle rekursiven Aufrufe haben diese Form. Zum Beispiel kann es mehrere rekursive Aufrufe in verschiedenen Zweigen geben (von case-Anweisungen oder solchen). Es ist natürlicher, sich diese als Auswahl eines Pfades durch einen Baum vorzustellen . Beachten Sie auch, dass die Aufrufe rekursiv sind (oder nicht), sodass Sie möglicherweise wissen, f (f x)wo der äußere Aufruf von frekursiv ist. Wie passt das in die Ansicht, dass es um Listen geht? Hier ist ein weiteres Beispiel: f : (Int -> Int) -> (Int -> Int)mit f g 0 = g 42und f g (n + 1) = f (f . g) n. Die Möglichkeiten sind endlos und einige sind nützlich.
Andrej Bauer
@AndrejBauer Die Frage nach Schwanz war Rekursion nicht nur Schwanz Anrufe , so würde ich das nicht als f (f x)zutreffend: bei der Beurteilung der äußeren f, die innere kein Endaufruf ist (es sei denn , f die Identität ist). If-Anweisungen können trivial umgeschrieben werden, um nicht im Tail-Aufruf zu verzweigen : if (c) then f a else f b == let x = if (c) then a else b in f x. Das letzte Beispiel ist ungültig, da f . gkeine Typprüfung durchgeführt wird. Trotzdem wäre es immer noch keine Schwanzrekursion: f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)ist kein Aufruf an f, sondern ein wirklich anderes Lambda. (weiter)
Ryan Reich
Ich würde tatsächlich sagen, dass es sich bei diesem Beispiel um eine gegenseitige Schwanzrekursion handelt , dh f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }wenn Sie dies in die Diskussion einbeziehen, ist jede rekursive Funktion, ob Schwanz oder nicht, zulässig und der Begriff wird bedeutungslos.
Antworten:
Strukturelle Rekursion: Rekursive Aufrufe werden für strukturell kleinere Argumente durchgeführt.
Schwanzrekursion: Der rekursive Aufruf ist das Letzte, was passiert.
Es ist nicht erforderlich, dass die Schwanzrekursion für ein kleineres Argument aufgerufen wird. Tatsächlich sind Schwanzrekursionsfunktionen häufig so konzipiert, dass sie für immer eine Schleife bilden. Hier ist zum Beispiel eine triviale Schwanzrekursion (nicht sehr nützlich, aber es ist eine Schwanzrekursion):
Wir müssen tatsächlich etwas vorsichtiger sein. Eine Funktion kann mehrere rekursive Aufrufe enthalten, und nicht alle müssen rekursiv sein:
Man spricht von rekursiven Schwanzaufrufen . Eine Funktion, deren rekursive Aufrufe alle schwanzrekursiv sind, wird dann als rekursive Schwanzfunktion bezeichnet.
quelle
Die Schwanzrekursion ist ein sehr einfacher Fall der strukturellen Rekursion, bei der es sich bei der fraglichen Struktur um eine verknüpfte Liste handelt . In der Sprache, die Sie wahrscheinlich hauptsächlich verwenden, ist diese Liste wahrscheinlich nicht wörtlich im Code enthalten. Vielmehr handelt es sich um eine konzeptionelle "Liste von Aufrufen der Funktion", ein Konzept, das möglicherweise nicht so ausgedrückt werden kann, wie es in dieser Sprache geschrieben wurde. In Haskell (meiner Sprache) kann jeder schwanzrekursive Funktionsaufruf tatsächlich durch Sequenzierungsaktionen in einer Literalliste ersetzt werden, deren Elemente buchstäblich "Aufrufe einer Funktion" sind, aber dies ist wahrscheinlich eine funktionale Sprache.
Die strukturelle Rekursion ist eine Methode zum Bearbeiten eines Objekts, das als Verbund aus anderen (möglicherweise zusammengesetzten) Objekten definiert ist. Ein Binärbaum ist beispielsweise ein Objekt, das Verweise auf zwei Binärbäume enthält, oder ist leer (daher handelt es sich um ein rekursiv definiertes Objekt). Weniger selbstreferenziell lässt ein Paar (t1, t2), das zwei Werte einiger Typen t1 und t2 enthält, eine strukturelle Rekursion zu, obwohl t1 und t2 nicht auch Paare sein müssen. Diese Rekursion hat die Form
das klingt nicht sehr tief.
Es ist häufig der Fall, dass eine strukturelle Rekursion nicht schwanzrekursiv sein kann , obwohl jede Art von Rekursion als Schwanzrekursion umgeschrieben werden kann (Beweis: Wenn Sie die ursprüngliche Rekursion ausführen, werden die Aktionen in einer bestimmten Reihenfolge ausgeführt, daher die Rekursion ist gleichbedeutend mit der Ausführung dieser bestimmten Abfolge von Aktionen, die, wie ich zuvor besprochen habe, eine Schwanzrekursion ist.
Entweder der Binärbaum oder das obige Paarbeispiel zeigen dies: Wenn Sie jedoch die rekursiven Aufrufe für die Unterobjekte anordnen, kann nur einer von ihnen die letzte Aktion sein. möglicherweise ist keiner von beiden, wenn ihre Ergebnisse auf irgendeine Weise kombiniert werden (z. B. Addition). Wie Andrej Bauer in seiner Antwort sagt, kann dies sogar mit nur einem rekursiven Aufruf geschehen, solange das Ergebnis geändert wird. Mit anderen Worten, für jeden Objekttyp außer denjenigen, die effektiv verknüpfte Listen sind (nur ein Unterobjekt ganz nach unten), ist strukturelle Rekursion keine Schwanzrekursion.
quelle
f x = (stuff defining x'); f x'
mit der Sequenzierung von Knoten in einer verknüpften Liste identisch ist, die wie folgt definiert istl = modify f : l
(im Haskell-State-Monad-Stil). Es war nicht nur die terminologische Ähnlichkeit für mich. Könnten Sie die Schwanzrekursion über binäre Bäume näher erläutern? Ich kann nur an die Linearisierung aus meinem vorletzten Absatz denken.f (f x)
wo der äußere Aufruf vonf
rekursiv ist. Wie passt das in die Ansicht, dass es um Listen geht? Hier ist ein weiteres Beispiel:f : (Int -> Int) -> (Int -> Int)
mitf g 0 = g 42
undf g (n + 1) = f (f . g) n
. Die Möglichkeiten sind endlos und einige sind nützlich.f (f x)
zutreffend: bei der Beurteilung der äußeren f, die innere kein Endaufruf ist (es sei denn , f die Identität ist). If-Anweisungen können trivial umgeschrieben werden, um nicht im Tail-Aufruf zu verzweigen :if (c) then f a else f b == let x = if (c) then a else b in f x
. Das letzte Beispiel ist ungültig, daf . g
keine Typprüfung durchgeführt wird. Trotzdem wäre es immer noch keine Schwanzrekursion:f g = \n -> if n == 0 then g 42 else f (f . g) (n - 1)
ist kein Aufruf anf
, sondern ein wirklich anderes Lambda. (weiter)f g = h where { h 0 = g 42; h n = f (f . g) (n - 1) }
wenn Sie dies in die Diskussion einbeziehen, ist jede rekursive Funktion, ob Schwanz oder nicht, zulässig und der Begriff wird bedeutungslos.