![]() |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Hier mal eben, der Vollständigkeit halber, die Variante mit (mehr oder weniger) konstanter Laufzeit:
Delphi-Quellcode:
EDIT: Das Kopieren der ganzen Methode ist im übrigen auch auf modernen Prozessoren praktisch nicht möglich, weil man normalerweise keinen Lesezugriff auf das eigene Programm hat (man müsste das Programm auf der Festplatte suchen und da raus kopieren) und vor allem, weil man nur in den Datenbereich schreiben kann und dieser (zumindest teilweise) durch Dinge wie das NX-Bit nicht einmal ausführbar ist. Also Kopieren ist nicht nur sinnlos, sondern auch praktisch unmöglich.
function fib(const n: Integer);
begin Result := round((power((1 + sqrt(5)) / 2, n) - power((1 - sqrt(5)) / 2, n))) / sqrt(5)); end; |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Nu werd ich mal korrigieren:
Zitat:
Natürlich gibt es für jeden rekursiven Algorithmus ein iteratives Äquivalent, welches teilweise völlig anders arbeitet, aber das ist nicht das Thema. Diese iterativen Äquivalente sind manchmal eleganter (Fibionacci), manchmal grauenvoll unleserlich (Ackermann). Von Quicksort kenne ich z.B. gar kein iteratives Äquivalent, das ohne einen selbstimplementierten Stack auskommt. Und zum Speicher: Die Rücksprungadresse wird natürlich auf dem Stack abgelegt, das ist an 'Mehrspeicher' aber auch alles. Das zu erwähnen, ist -finde ich- nicht der Rede wert, aber an sich völlig o.k. Ich würde mir mal z.B. hier im Forum weitere Meinung einholen und dann deinen Lehrer mit diesen (unseren) Meinungen konfrontieren. Vielleicht sieht er seinen Fehler ein. Er ist auch gerne eingeladen, sich hier zu dem Thema zu äußern. Ich würde ihn aber erstmal bitten, seinen Kommentar zu erklären ('Damit ich das verstehe, Herr Lehrer. Ich will ja was lernen. *schleim*") Zitat:
|
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Zitat:
|
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Zitat:
Dann muss ich meine Ausführungen korrigieren: Der Lehrer vergleicht Äpfel mit Birnen. Mal sehen: Eine rekursive Fibionacci-Implementierung, die auch recht flott ist:
Delphi-Quellcode:
Probier die mal...
Var
Buffer : Array [0..1000] Of Integer; // Einmalig mit '0' initialisieren function fib_rek(pZahl:Integer):Integer; begin if Buffer[pZahl]>0 then Result := Buffer[pZahl] Else if pZahl < 3 Then Result := 1 Else Result := fib_rek(pZahl-1) + fib_rek(pZahl-2); Buffer[pZahl] := Result; end; Zitat:
|
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Liste der Anhänge anzeigen (Anzahl: 2)
Man kriegt das auch noch effektiver hin. Und damit willkommen beim DP-Fibonacci-Effizienzkontest! :mrgreen:
Heute im Ring: Die iterative Variante, die Standard-Rekursive mit Buffer und eine etwas "aufpoliert" eigene rekursive Variante, auch mit Buffer. Dem Borg seine O(1)-Lösung sei hier außen vor gelassen, die ist ja geschummelt. Die aufpolierte Fassung macht nichts anderes, als fib(n) = fib(n-1) + fib(n-2) aufzudröseln, denn fib(n-1) ist ja nichts anderes als fib(n-2)+fib(n-3), also ist fib(n) = 2*fib(n-2)+fib(n-3) was dann wieder 3*fib(n-3)+2*fib(n-4) entspricht. Die Koeffizienten sind wiederum Fibonaccizahlen, da allerdings aufsteigende. Das ganze trifft sich dann in der Mitte, und ergibt sich, je nach Teilbarkeit von n durch 2, entweder zu fib(n) = fib(n div 2)*(fib(n div 2 +1)+fib(n div 2 -1)) für ungerade n, oder fib((n+1) div 2)²+fibOwn(n div 2)², was jede Menge Aufrufe spart und das ganze zumindest in dieser Implementation 10 mal schneller als den Iterativen macht. Und die Testergebnisse für fib(10000) befinden sich, ebenso wie der (schnell'n'dreckige) Code, im Anhang. Die Zahlen in den ProgressBars ist die jeweilige Dauer in ms. Benutzt wird eine olle BigInt-Klasse, weil die da rauskommenden Zahlen doch etwas größer als 2^63-1 sind. aber vermutlich geht das ganze noch ein Tucken schneller, ich bin gespannt :firejump: |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Den vergleich mit rekursiv und iterativ bei der fibonacci folge hatten wir mal und das lief dann auf sowas raus:
Ab nem genügend großen n benötigt die iterative Methode ein paar Minuten/Stunden während es rekursiv so lange dauert wie das Universum alt ist... Das liegt eben daran dass dir rekursive Methode dieselben Ergebnisse öfter berechnet. Ich kann sowieso nur von der rekursiven Methode abraten. Deshlab ist mir schon ein Programm regelmäßig abgestürzt wegen StackOverflow. Leider kam keine Fehlermeldung und deshalb saß ich recht lange dran bis ich gemerkt hab worans lag... |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Zitat:
Selbst wenn der Übergabeparameter über ein Register übergeben wird, muss es doch auf dem Stack gesichert werden. Hätte die Funktion lokale Variablen, wäre der Speicherplatz dafür ebenfalls auf dem Stack alloziert. |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Zitat:
Der Vergleich mit der "Wurzelformel" ist auch aus einem anderen Grunde falsch: Die Berechnung der Wurzel, sofern sie exakt und nicht nur näherungsweise erfolgt, erfordert wegen deren Irrationalität einen unendlich hohen und damit unendlich langen Rechenaufwand (so daß man gar nicht zu den eigentlichen Rechenoperationen mit den Wurzeln als Finale gelangt) und ist damit der irgendwann endenden Rekursion unendlich weit unterlegen! |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Zitat:
Natürlich wächst der Stack bei einem rekursiven Aufruf (wie be jedem Aufruf) um die lokalen Variablen zuzüglich der Übergabeparameter sowie der Rücksprungadresse. Etwas analoges müsste man implementieren, wenn man ein Verfahren iterativ programmiert. Aber wie gesagt: Die Aufgabe des Threaderstellers behandelt gar keine Diskussion "rekursiv vs. iterativ", sondern "langsamer Apfel mit schneller Birne". Beide Früchtchen berechnen die Fibionacci-Zahl Fib(N), der Apfel macht das rekursiv, die Birne (anders) iterativ. "Namenlozer" zeigt, das die Birne viel viel schneller ist. Man könnte also zu dem Ergebnis kommen, das iterativ implementierte Algorithmen stets schneller sind, als ihre rekursiven Pendants. Genausogut könnte man Bubblesort (iterativ) mit Quicksort (rekursiv) vergleichen: Beide Früchte sortieren, aber die Iterative Variante ist viel viel langsamer. Folgt nun daraus: Iterativ ist langsamer als rekursiv? |
Re: Rekursiver Aufruf - Was geht da eigentlich vor sich?
Man sollte noch eine weitere Rekursionsvariante erwähnen: In #24 hat alzaimar die naive Rekursion durch
![]() Genauso wie Zitat:
Delphi-Quellcode:
In Delphi hat diese Variante genau die gleiche Zeit- und Platzkomplexität wie die Memoisationsvariante, der große Witz daran ist aber, dass die Funktion
function Fib(n)
function Loop(i, j, n') begin if n' = n then Result := i else Result := Loop(j, i + j, n' + 1); end; Loop(0, 1, 0); end; ![]() ![]() Natürlich ist diese Variante nicht ganz so leserlich wie die naive Rekursion, aber wenigstens diese dämliche Hilfsvariable entfällt ;P . Vor allem wollte ich eben auf diese direkte Äquivalenz der beiden Methoden hinaus. Um das ganze mal zusammenzufassen ;) :
Code:
Aber in welche Klasse fällt denn bitte inheriteds netter Algo, O(log² n) :gruebel: ?
.
Zeit Stack Heap iterativ Θ(n) Θ(1) 0 /rekursiv mit Tail Calls naive Rekursion Θ(fib(n)) = Θ(φ^n) Θ(n) 0 + Memoisation Θ(n) Θ(n) Θ(n) |
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:43 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz