| 

.NET C# Java Javascript Exception

1
Als Neuling in der objektorientierten Programmierung suche ich eine Erklärung zur Funktionsweise der Rekursion. In meinem Viusal C# 2008 Buch finde ich leider nichts zu diesem Thema.

Oder sollten Methoden sich in C# aus irgendwelchen Gründen gar nicht selbst aufrufen ?

Für jeden Tipp dankbar,
el
17.09.2009
elcarus 63 3
Was brauchst du den da eine Erklärung?
Eine Rekursion kann natürlich auch unter C#, sowie auch unter anderen Sprachen Sinn machen.
Lars Schmitt 17.09.2009
@Lars, da er "Neuling in der objektorientierten Programmierun" ist, hat er vieleicht nicht verstanden was eine Rekursion ist oder wozu man sie gebrauchen kann?!
Floyd 17.09.2009
6 Antworten
2
Zu Schulzeiten hat sich mein Informatiklehrer ein sehr schönes anschauliches Beispiel zur Rekursion ausgedacht.
Wir Schüler saßen im Halbkreis um den Lehrer und er fragte den Ersten 'Was ist die Fakultät von 15?' (ja Fakultät scheint beliebt um Rekursion zu erklären)
Der Schüler war natürlich etwas überrascht und zückte seinen Taschenrechner, woraufhin der Lehrer meinte 'Machs dir einfach. Du weißt 15! = 14! * 15, frag einfach deinen Nachbarn, was 14! ist.'
Das ging dann einmal die Runde rum (Rekursionsabstieg), bis wir bei 1! angekommen waren (Abbruchbedingung). Der für 1! verantwortliche Schüler gab die Antwort seinem Nachbarn, der dann widerrum 2! ausrechnen konnte und das Ergebnis ebenfalls seinem Nachbarn gab, und so weiter, bis der erste Schüler nun 15! ausrechnen konnte (Rekursionsaufstieg).
18.09.2009
Charon 151 1 2
Brilliant!
Jörg W Mittag 18.09.2009
5
Praktische Beispiele für Rekursionen sind schwirig zu erklären. Eine Rekursion ist, wenn sich eine Funktion immer wieder selbst aufruft. Theoretisch kann Sie das unendlich lange machen, wobei es keine sinn machen würde. Einen Sachverhalt den man typischerweise mit einer Rekusion erklären könnte wäre die Abstammung einer Person. "Otto ist das Kind von Frank und Steffi, Frank ist Kind von Olfa und Magith, Steffi ist Kind von Klaus und Berta, Olfa ist Kind von Heinz und Inge ..."
Jeweils immer X ist Kind von Y und Z wobei Y und Z ebenfalls wieder Kinder von Y1 und Z1 und Y2 und Z2 sind und so weiter.

Aus Performancesicht, ist es besser ohne Rekursionen zu arbeiten weil dan die Register nicht neu inizialisiert und geschrieben werden müssen. Aus programmiersicht sind rekursionen die elegantere Lösung weil leichter zu warten und übersichtlicher.

Auf http://de.wikipedia.org/wiki/Rekursion wird die Rekursion etwas ausführlicher erklärt und es werden auch, etwas weiter unten, Programmierbeispiel gezeigt. Sehr interessant ist das Programmierbeispiel 2, welche zeigt was eine Rekursion ist, und wie man sie auflößt (dh. das selbe Problem ohne Rekusion lößt).

Mit Rekursion:
//Quelle Wikipedia: http://de.wikipedia.org/wiki/Rekursion
// Absatz: "Programmierbeispiel II, Fakultätsberechnung"
fakultät_rekursiv(n)
{
wenn n <= 1
dann return 1
sonst return ( n * fakultät_rekursiv(n-1) )
}


Das selbe Problem ohne Rekursion:
//Quelle Wikipedia: http://de.wikipedia.org/wiki/Rekursion Absatz:
// Absatz: "Programmierbeispiel II, Fakultätsberechnung"
fakultät_iterativ(n)
{
fakultät = 1
faktor = 2
solange faktor <= n
{
fakultät = fakultät * faktor
faktor = faktor + 1
}
return fakultät
}
17.09.2009
Floyd 11,0k 3 9
Floyd 11,0k 3 9
Da hätte ich mir meinen Beitrag sparen können ;)
gfoidl 17.09.2009
Zwei dumme, ein Gedanke ;)
Floyd 17.09.2009
5
Rekursion hat nichts mit einer Programmiersprache zu tun sondern ist ein Begriff der von der Mathematik geprägt ist. Einfach ausgedrückt wird dabei eine Folge (von Zahlen) derart definiert dass das nächste Glied aus dem vorigen berechnet wird. Somit wird immer weiter zurückgegangen bis ein Startpunkt erreicht ist. Dann werden die Schritte wieder vorwärts gelaufen und die nun bekannte Zwischenergebnisse verarbeitet.

Aufbauend auf dieser Definition wird das auch in der Programmierung angewandt. Es gibt Algorithmen die sich nicht iterativ - d.h. der Reihe nach jedes Element - lösen lassen und dann wird dies rekursiv gelöst. Es gibt auch Probleme dies sich rekursiv einfacher lösen lassen.

Das klassische Beispiel ist die Berechnung der Fakultät:
public int Fakultät(int n)
{
if (n <= 1)
return 1;

return n * Fakultät(n - 1);
}

Was passiert:

  • die Methode wird aufgerufen, zB mit 2
  • die if-Bedinung ergibt false und somit wird dieselbe Methode wieder aufgerufen aber mit 1
  • die if-Bedinung ergibt true und somit wird 1 zurückgegeben -> Startpunkt
  • das Ergebnis dieses Rekursionschritts wird für das return von Schritt 2 verwendet und das Ergebnis berechnet. Da der Startpunkt erreicht wurde ist keine weitere Rekursion nötig.

Du kannst den Code kopieren und mit dem Debugger schauen was er macht.

Wie bereits erwähnt gibt es Algorithmen/Probleme die sich nicht/nur schwer iterativ lösen lassen. Dazu zählen zB

  • Türme von Hanoi
  • Determinatenberechnung mit dem Laplaceschen Entwicklungssatz
  • Berechnung von Fibonacci-Zahlen
  • anderes was auf Teile & Herrsche basiert


Es gibt also keinen Grund warum sich ein Methode nicht selbst aufrufen soll. Für den Prozessor gibt es keinen Unterschied ob er dieselbe oder eine andere Methode aufruft. Es ist nur darauf zu achten dass keine Endlosrekursion auftritt. Dies würde in einen 'Stack-Overflow' resultieren.

Bisher habe ich nur von der sogenannten 'direkten Rekursion' geschrieben. Es gibt auch die 'indirekte Rekursion'. D.h. Methode A ruft Methode B auf und diese wiederum Methode A solange bis ein Startpunkt erreicht ist. Vom Prinzip her ändert sich nichts.
17.09.2009
gfoidl 9,1k 3 5
3
Es gibt Probleme, die sich rekursiv "schöner" lösen lassen, als iterativ. Wie schon erwähnt bedeutet Rekursion, dass sich eine Funktion bis zu einer Abbruch-Bedingung selbst wieder aufruft, meist mit Parametern, die sich aus dem vorherigen Aufruf ergeben.

Das Problem mit Rekursion ist, dass man oft relativ lange braucht, um auf einen geeigneten Algorithmus zu kommen, der dann oft aber sehr elegant ist.

Ein Beispiel aus der Mathematik wurde schon gegeben: die Fakultät. Ein weiteres wären die Fibonacchi Zahlen. Eine Fibonacchi-Zahl ist definiert als die Summe der beiden vorangegangenen Fibonacchi-Zahlen. Aus dem Kopf müsste das so aussehen:

public int Fib(int i)
{
if (i < 0) throw new ArgumentException("Fib ist für negative Zahlen nicht definiert!");
if (i == 0 || i == 1) return 1;
return Fib(i-1) + Fib(i-2);
}


Ein Problem aus der Informatik wäre z.B. das Hinzufügen aller Ordner der Festplatte zu einem Baum-Control. Hier kann man einen rekursiven Algorithmus entwerfen, der in folgenden Schritten arbeitet:

  • Erstelle eine interne Liste aller Unterordner in einem gegebenen Ordner
  • Für jeden dieser Unterordner: Füge einen Knoten in den Baum ein und rufe rekursiv für diesen Ordner auf
  • Wenn es keine Unterordner mehr gibt, höre auf


In Pseudocode sähe das etwa so aus:

funktion Unterordner(string root, TreeNode rootNode)
{
List<string> unterordner = HoleUnterordner(root);
foreach (string o in unterordner)
{
neuerKnoten = ErzeugeNodeIn(rootNode, o);
Unterordner(o, neuerKnoten);
}
}
18.09.2009
balu 216 1 3
Die Sache mit den Ordner und Unterordner ist ein sehr gutes Beispiel. Wieso bin ich da nicht drauf gekommen :D
Floyd 18.09.2009
Das wiederum kann ich Dir nicht sagen ^^
balu 18.09.2009
Ein rekursiver Algorithmus lässt sich leichter erstellen als ein iterativer. Hast du das verwechselt? ZB dein Beispiel mit dem Unterordner (das ich auch gut finde) würde mir schwer fallen iterativ zu lösen, rekursiv ist es trivial.
gfoidl 18.09.2009
Hab die Lösung mal als eigene Antwort gepostet.
Floyd 20.09.2009
1
Auf die Frage hin wie sich das Beispiel der Unterordner auch ohne Rekursion (also iterativ) lösen läßt, hier mal der entsprechende Quellcode:

DirectoryInfo basedir = new DirectoryInfo("C:");
Queue<DirectoryInfo> qListSubFolders = new Queue<DirectoryInfo>();

qListSubFolders.Enqueue(basedir);

while (qListSubFolders.Count>0) {
DirectoryInfo subFolderDir = qListSubFolders.Dequeue();
Console.WriteLine(subFolderDir.FullName);

if(subFolderDir.GetDirectories().Length > 0)
foreach (DirectoryInfo sF in subFolderDir.GetDirectories())
qListSubFolders.Enqueue(sF);
}
20.09.2009
Floyd 11,0k 3 9
Floyd 11,0k 3 9
Durch Queue/Stack wird die Rekursion nur verlagert - meiner Anschauung nach, obwohl die Methode nicht mehr rekursiv aufgerufen wird.

Aber schöne Lösung!
gfoidl 20.09.2009
0
Wie Floyd schon richtig bemerkt hat war mir das Prinzip der Rekursion nicht so ganz klar. Jetzt habe ich es verstanden und werde mir am Abend noch den geposteten Code im Visual Studio genauer anschauen.

Vielen Dank für eure ausführlichen Beiträge Floyd und gfoidl!
18.09.2009
elcarus 63 3
1
Um Rekursion zu verstehen muß man zuerst Rekursion verstehen ;)
Charon 18.09.2009
1
Ich werde ja nicht müde zu erwähnen, dass dies kein Forum ist - es macht also keinen Sinn, als Fragesteller selbst auch eine Antwort zu posten, weil diese durch Auf- und Abwertungen nicht an der Stelle bleibt, wo man sie gepostet hat. Also bitte: nicht selbst antworten, sondern ggf. editieren der Frage oder Kommentar!!
balu 18.09.2009

Stelle deine .net-Frage jetzt!