Rekursionsfrage



  • freakC++ schrieb:

    In einem letzten Thread habe ich das return vor dem rekursiven Aufruf vergessen. Dass dieses dahingehört ist mir am oberen Beispiel klar, aber warum muss ich beim ggT vor dem Selbstaufruf ein return schreiben?

    Aus demselben Grund. Ohne return keine Rückgabe.

    Mir geht es doch eigentlich um das "b" ganz am Schluss und nichts das ich vom Stack noch draufaddieren müsste wie bin der Multiplizierung. Warum ist das so?

    Die ganzen ineinandergeschachtelten Funktionsaufrufe müssen wieder abgewickelt werden. Jeweils der Rückgabewert der einen Instanz wird weiter zurückgegeben, ohne in diesem Fall noch etwas damit zu machen.



  • icarus2 schrieb:

    Ich fürchte ich habe deine Frage nicht ganz verstanden. Ich versuchs jetzt trotzdem mal:

    Der ggT ist gemäss dem Algorithmus von Euklid das b im letzen Rekursionsaufruf (du brauchst das return vor dem Selbstaufruf, damit die Rekursion abbricht). Und dieses wird schlussendlich auch zurückgegeben.

    Ich denke hier ist etwas falsch:
    3+mult(3,3)
    3+3+mult(3,2)
    3+3+3+mult(3,1) // Hier bricht der Algorithmus bereits ab, weil 3 % 1 == 0, also wird 1 zurückgegeben.
    3+3+3+3+mult(3,0) // Wird gar nicht mehr ausgeführt
    3+3+3+3+0 😉

    Ja, hast Du. Das Beispiel gehört zur Berechnung des Produkts. Damit wollte ich zeigen, dass ich hier verstanden habe, warum das return dahin gehört.

    icarus2 schrieb:

    Es müsste dann übrigens heissen if(tmp == 0)

    peinlich, peinlich! Ich hab es aus Delphi übersetzt und da passiert das schonmal leicht.

    Bashar schrieb:

    Die ganzen ineinandergeschachtelten Funktionsaufrufe müssen wieder abgewickelt werden. Jeweils der Rückgabewert der einen Instanz wird weiter zurückgegeben, ohne in diesem Fall noch etwas damit zu machen.

    Aber es gibt doch hier nur einen Rückgabewert, nämlich das letzte "b".

    Danke
    lg, freakC++



  • Es sieht aber intern nicht so aus wie du beschrieben hast.

    Ja, es gibt nur den letzen Rückgabewert b. Aber dieser muss durch alle Rekursionsstufen zurückgegeben (hochgereicht) werden.
    return ggT(a,tmp); hat immer den Wert b.



  • freakC++ schrieb:

    Aber es gibt doch hier nur einen Rückgabewert, nämlich das letzte "b".

    Ja, aber dieses ist etwas ganz anderes als das b, was vom Hauptprogramm reingekommen ist - bei jedem Aufruf der Funktion ggT() wird ein weiteres Stück vom Stack reserviert und dort eine eigene Version von a, b und tmp untergebracht (zusammen mit einigen Verwaltungs-Informationen, um beim Funktionsende alles wieder aufräumen zu können) - womöglich auf für eine temporäre Variable, die den Rückgabewert des rekursiven Aufrufs aufnehmen kann. Beim return wird der dort genannte Wert in den Speicherbereich des Aufrufers geschrieben und kann anschließend weiterverarbeitet werden (daß sich diese Weiterverarbeitung hier auf eine Rückgabe beschränkt, hat darauf keinen Einfluß).



  • @freakC++: Bitte schreibe am Ende, wenn das Problem gelöst würde, wer von den Helfern daran den größten Einfluß hatte.



  • volkard schrieb:

    @freakC++: Bitte schreibe am Ende, wenn das Problem gelöst würde, wer von den Helfern daran den größten Einfluß hatte.

    Bin ja schon ruhig.



  • volkard schrieb:

    @freakC++: Bitte schreibe am Ende, wenn das Problem gelöst würde, wer von den Helfern daran den größten Einfluß hatte.

    Ich, da ich als einziger im letzen Thread schon erkannt habe, wo sein Denkfehler ist und ein rekursiver Funktionsaufruf nichts besonderes ist.
    Stattdessen wurde sich aber lieber über Kompileroptimierungsmöglichkeiten von Rekursionen unterhalten.
    :p



  • CStoll schrieb:

    Ja, aber dieses ist etwas ganz anderes als das b, was vom Hauptprogramm reingekommen ist - bei jedem Aufruf der Funktion ggT() wird ein weiteres Stück vom Stack reserviert und dort eine eigene Version von a, b und tmp untergebracht (zusammen mit einigen Verwaltungs-Informationen, um beim Funktionsende alles wieder aufräumen zu können) - womöglich auf für eine temporäre Variable, die den Rückgabewert des rekursiven Aufrufs aufnehmen kann. Beim return wird der dort genannte Wert in den Speicherbereich des Aufrufers geschrieben und kann anschließend weiterverarbeitet werden (daß sich diese Weiterverarbeitung hier auf eine Rückgabe beschränkt, hat darauf keinen Einfluß).

    Ahh..danke! Es geht als vor allem um das Aufräumen des Stacks, das ohne das return vor dem Selbstaufruf nicht funktionieren würde. Habe ich das richtig verstanden?

    volkard schrieb:

    @freakC++: Bitte schreibe am Ende, wenn das Problem gelöst würde, wer von den Helfern daran den größten Einfluß hatte.

    Das verstehe ich wiederum nicht. Habe ich was falsch gemacht? Was meinst Du?



  • freakC++ schrieb:

    Ahh..danke! Es geht als vor allem um das Aufräumen des Stacks, das ohne das return vor dem Selbstaufruf nicht funktionieren würde. Habe ich das richtig verstanden?

    Nein, der Stack räumt sich auch ohne return auf. Es geht um das zurückgeben des Wertes.

    Stell dir vor, wir implementieren mult nicht-rekursiv, eher pseudo-rekursiv:

    int mult2(int a, int b)
    {
      if(b == 0)
        return b;
      throw "maximum recursion depth exceeded";
    } 
    
    int mult1(int a, int b)
    {
      if(b == 0)
        return b;
      return a+mult2(a,b-1);
    } 
    
    int mult(int a, int b)
    {
      if(b == 0)
        return b;
      return a+mult1(a,b-1);
    }
    

    Das funktioniert genauso wie das rekursive mult, nur greift mult auf eine Funktion mult1 zurück, die wiederum auf mult2 zurückgreift. An der Stelle hab ich dann mal abgebrochen, man kann halt nur maximal mit 2 multiplizieren.

    Jetzt frag ich dich: Macht es ohne return Sinn?



  • freakC++ schrieb:

    CStoll schrieb:

    Ja, aber dieses ist etwas ganz anderes als das b, was vom Hauptprogramm reingekommen ist - bei jedem Aufruf der Funktion ggT() wird ein weiteres Stück vom Stack reserviert und dort eine eigene Version von a, b und tmp untergebracht (zusammen mit einigen Verwaltungs-Informationen, um beim Funktionsende alles wieder aufräumen zu können) - womöglich auf für eine temporäre Variable, die den Rückgabewert des rekursiven Aufrufs aufnehmen kann. Beim return wird der dort genannte Wert in den Speicherbereich des Aufrufers geschrieben und kann anschließend weiterverarbeitet werden (daß sich diese Weiterverarbeitung hier auf eine Rückgabe beschränkt, hat darauf keinen Einfluß).

    Ahh..danke! Es geht als vor allem um das Aufräumen des Stacks, das ohne das return vor dem Selbstaufruf nicht funktionieren würde. Habe ich das richtig verstanden?

    Nein, der Compiler wird am Ende der Funktion versuchen, eine eigene return-Anweisung zu ergänzen, um den Programmfluß wieder um Aufrufer zurückzuleiten (die Alternative wäre schmlimmer). Allerdings ist afaik nicht definiert, welche Wert er dann an den Aufrufer zurückgeben wird - dadurch bekommst du dann höchstwahrscheinlich Müll im Hauptprogramm geliefert.
    Allerdings sollte dein Compiler dich auch warnen, wenn er zu diesem Schritt gezwungen ist.



  • Ich versuchs auch mal zu erklären...

    int Endlos(int a) {
    return Endlos(a)
    }
    

    Ich hoffe es ist klar, dass das eine Endlosschleife bildet (die irgendwann absürzt).
    Um rauszufinden was er da genau returnen soll, muss er "int Endlos(int a)" auswerten, um das auszuwerten muss er "int Endlos(int a)" auswerten, um das ...

    Man braucht also für eine Rekursion immer eine Abbruchbedingung, etwas was sagt "Ich bin hier fertig", etwas was eben nicht erneut die Funktion aufruft und die Rekursion fortsetzt. Deswegen braucht eine rekursive Funktion prinzipiell immer 2-mal return. Einmal zum weitermachen, und einmal zum aufhören.
    Man hört normalerweise auf wenn man sein Ergebnis hat, und das ist in beiden deiner Beispiele "b" - das Ergebnis, das zurückgegeben wird.



  • kleiner Troll schrieb:

    Ich versuchs auch mal zu erklären...

    Netter Vortrag, aber ein wenig am Thema vorbei. Außerdem ist es weder notwendig noch ausreichend, in einer rekursiven Funktion genau zwei return zu haben.



  • Danke für die Antworten. Ich denke, ich habe es verstanden. Wenn ich das return nicht schreiben würde, dann gäbe es für meisten Aufrufe keinen Rückgabetyp und das wäre gegen den Sinn einer Funktion.

    lg, freakC++


Anmelden zum Antworten