Datenstruktur Baum und Rekursion Software Entwicklung 1

Ähnliche Dokumente
Einführung in die Informatik: Programmierung und Software-Entwicklung, WS 11/12. Kapitel 13. Bäume. Bäume

Vorlesung Datenstrukturen

Verkettete Datenstrukturen: Bäume

Geordnete Binärbäume

Algorithmen und Datenstrukturen 1

Bäume. Text. Prof. Dr. Margarita Esponda SS 2012 O4 O5 O6 O ALP2-Vorlesung, M. Esponda

Programmieren lernen mit Groovy Rekursion Rekursion und Iteration

Bäume. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 10: Collections 4. Inhalt. Bäume. Einführung. Bäume.

Der linke Teilbaum von v enthält nur Schlüssel < key(v) und der rechte Teilbaum enthält nur Schlüssel > key(v)

Folge 19 - Bäume Binärbäume - Allgemeines. Grundlagen: Ulrich Helmich: Informatik 2 mit BlueJ - Ein Kurs für die Stufe 12

Binärbäume. Prof. Dr. E. Ehses,

Probeklausur: Programmierung WS04/05

368 4 Algorithmen und Datenstrukturen

Programmierkurs Java

II.3.1 Rekursive Algorithmen - 1 -

Programmiertechnik II

5.14 Generics. Xiaoyi Jiang Informatik I Grundlagen der Programmierung

Rekursive Funktionen

Komplexität von Algorithmen

Prüfung Informatik D-MATH/D-PHYS :00 11:00

Übungen zu Programmierung I - Blatt 8

Einführung Datentypen Verzweigung Schleifen Funktionen Dynamische Datenstrukturen. Java Crashkurs. Kim-Manuel Klein

Algorithmen und Datenstrukturen Tafelübung 4. Jens Wetzl 15. November 2011

Wiederholung ADT Menge Ziel: Verwaltung (Finden, Einfügen, Entfernen) einer Menge von Elementen

Suchen und Sortieren

3.2 Binäre Suche. Usr/local/www/ifi/fk/menschen/schmid/folien/infovk.ppt 1

Bäume, Suchbäume und Hash-Tabellen

13. Binäre Suchbäume

Übersicht. Datenstrukturen und Algorithmen Vorlesung 5: Rekursionsgleichungen (K4) Übersicht. Binäre Suche. Joost-Pieter Katoen. 20.

To know recursion, you must first know recursion. Borchers: Programmierung für Alle (Java), WS 06/07 Kapitel 17 1

JAVA - Methoden - Rekursion

Algorithmen und Datenstrukturen Suchbaum

Wiederholung Wozu Methoden? Methoden Schreiben Methoden Benutzen Rekursion?! Methoden. Javakurs 2012, 3. Vorlesung

Prof. Dr. Heinrich Müller; Dr. Frank Weichert 7. September 2015

Informatik II, SS 2014

1. Grundlegende Konzepte in Java (6 Punkte)

Grundlagen der Programmierung

Eine Baumstruktur sei folgendermaßen definiert. Eine Baumstruktur mit Grundtyp Element ist entweder

12. Rekursion Grundlagen der Programmierung 1 (Java)

Kapitel 5: Abstrakte Algorithmen und Sprachkonzepte. Elementare Schritte

Einführung Datentypen Verzweigung Schleifen. Java Crashkurs. Kim-Manuel Klein May 4, 2015

Fortgeschrittene Programmiertechnik Klausur SS 2015 Angewandte Informatik Bachelor

Teil 1: Suchen. Problemstellung Elementare Suchverfahren Hashverfahren Binäre Suchbäume Ausgeglichene Bäume. B-Bäume Digitale Suchbäume Heaps

Algorithmen & Datenstrukturen 1. Klausur

Algorithmen und Datenstrukturen

Funktionale Programmierung. Funktionale Programmierung: Vorlesungsüberblick. Eigenschaften rein funktionaler Programmierung

MB2-ALG, SS15 Seite 1 Hauptklausur, geschrieben am

Vorname:... Matrikel-Nr.:... Unterschrift:...

10. Kapitel (Teil1) BÄUME GRUNDLAGEN. Algorithmen & Datenstrukturen Prof. Dr. Wolfgang Schramm

Datenstrukturen & Algorithmen

Java Kurs für Anfänger Einheit 5 Methoden

Suchbäume. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Allgemeine Hinweise: TECHNISCHE UNIVERSITÄT MÜNCHEN. Name Vorname Studiengang Matrikelnummer. Hörsaal Reihe Sitzplatz Unterschrift

14. Rot-Schwarz-Bäume

Probeklausur: Programmierung WS04/05

Einführung in die Programmierung

Copyright, Page 1 of 8 AVL-Baum

Lemma Für jede monotone Grammatik G gibt es eine kontextsensitive

Advanced Programming in C

Speicher und Adressraum

1. Typen und Literale (6 Punkte) 2. Zuweisungen (6 = Punkte)

Dynamische Programmierung. Problemlösungsstrategie der Informatik

Programmieren in C. Rekursive Funktionen. Prof. Dr. Nikolaus Wulff

Rekursion. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Algorithmik II. a) Fügen Sie in einen anfangs leeren binären Baum die Schlüsselfolge 20, 28, 35, 31, 9, 4, 13, 17, 37, 25 ein.

Programmierung 1 (Wintersemester 2012/13) Erklärung 5 (Prä- und Postordnung)

Entscheidungsbäume. Definition Entscheidungsbaum. Frage: Gibt es einen Sortieralgorithmus mit o(n log n) Vergleichen?

11. Rekursion, Komplexität von Algorithmen

Tutoraufgabe 1 (2 3 4 Bäume):

MafI I: Logik & Diskrete Mathematik (F. Hoffmann)

Programmieren I. Kapitel 5. Kontrollfluss

AVL-Bäume Analyse. Theorem Ein AVL-Baum der Höhe h besitzt zwischen F h und 2 h 1 viele Knoten. Definition Wir definieren die nte Fibonaccizahl:

Abstrakte Klassen und Induktive Datenbereiche

Grundlagen der Programmierung Prof. H. Mössenböck. 6. Methoden

Repetitorium Informatik (Java)

Algorithmen & Programmierung. Rekursive Funktionen (1)

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

Kapiteltests zum Leitprogramm Binäre Suchbäume

Informatik II, SS 2014

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

4. Jeder Knoten hat höchstens zwei Kinder, ein linkes und ein rechtes.

Stapel (Stack, Keller)

Rekursion. Annabelle Klarl. Einführung in die Informatik Programmierung und Softwareentwicklung

Informatik II. PVK Part1 Severin Wischmann n.ethz.ch/~wiseveri

Einführung in die Informatik 1

Kap. 4.2: Binäre Suchbäume

Datenstrukturen und Algorithmen

Datenstrukturen & Algorithmen Lösungen zu Blatt 6 FS 14

Was bisher geschah. deklarative Programmierung. funktionale Programmierung (Haskell):

Kapitel 4: Dynamische Datenstrukturen. Algorithmen und Datenstrukturen WS 2012/13. Prof. Dr. Sándor Fekete

Fortgeschrittene Programmiertechnik Klausur WS 2014/15 Angewandte Informatik Bachelor

Programmierkurs Python I

JAVA - Methoden

JAVA für Nichtinformatiker - Probeklausur -

Übersicht. 4.1 Ausdrücke. 4.2 Funktionale Algorithmen. 4.3 Anweisungen. 4.4 Imperative Algorithmen Variablen und Konstanten. 4.4.

Kapitel 9. Komplexität von Algorithmen und Sortieralgorithmen

Einführung in die Informatik für Hörer aller Fakultäten II. Andreas Podelski Stephan Diehl Uwe Waldmann

Grundlagen der Programmierung 2. Bäume

Es sei a 2 und b 2a 1. Definition Ein (a, b)-baum ist ein Baum mit folgenden Eigenschaften:

Einführung in die Informatik für Naturwissenschaftler und Ingenieure (alias Einführung in die Programmierung)

Transkript:

Datenstruktur Baum und Rekursion Software Entwicklung 1 Annette Bieniusa, Mathias Weber, Peter Zeller 1 Datenstruktur Baum Bäume gehören zu den wichtigsten in der Informatik auftretenden Datenstrukturen. [Ottmann, Widmayer: Algorithmen und Datenstrukturen, 5. Auflage, Springer 2012] Baumstrukturen finden sich in verschiedenen Kontexten in der Informatik wieder: Syntaxbäume, Darstellung von Termen, Dateisysteme, Darstellung von Hierarchien (z.b. Typhierarchie), Implementierung von Datenbanken etc. Führen wir zunächst einige Begriffe ein: In einem endlich verzweigten Baum hat jeder Knoten endlich viele Kinder. Einen Knoten ohne Kinder nennt man ein Blatt, einen Knoten mit Kindern einen inneren Knoten oder Zweig. Den Knoten ohne Elternknoten nennt man Wurzel. Zu jedem Knoten k gehört ein Unterbaum, nämlich der Baum, der k als Wurzel hat. In einem Binärbaum hat jeder Knoten maximal zwei Kinder. i In unserer Vorlesung behandeln wir eine bestimmte Art von Bäumen, sogenannte gerichtete gewurzelte Bäume, bei denen wir stets einen Knoten als Wurzelknoten auszeichnen und die Verbindung von Eltern- zu Kindknoten (d.h. weg von der Wurzel) ausgerichtet ist. In der Graphentheorie werden allgemeinere Arten von Bäumen definiert, dazu erfahren Sie mehr in den Vorlesungen FGdP oder Graphentheorie. 1

Frage 1: Markieren Sie im folgenden Baum einen Wurzelknoten! Welche Knoten sind Blätter? Welches sind innere Knoten? Handelt es sich um einen Binärbaum? 1.1 Markierte Bäume Ein Baum heißt markiert, wenn jedem Knoten k ein Wert/eine Markierung m(k) zugeordnet ist. :BinTree root 5 10 3 4 5 9 1 Um einen markierten Binärbaum mit ganzen Zahlen als Markierung zu implementieren, benötigen wir zunächst eine Klasse für die Knoten. Diese erhält Attribute für die Markierung und die beiden Kindknoten left und right. 2

// Repraesentiert die Knoten eines Baums mit Markierungen class TreeNode { private int mark ; private TreeNode left ; private TreeNode right ; TreeNode ( int mark, TreeNode left, TreeNode right ) { this. mark = mark ; this. left = left ; this. right = right ; // Liefert die Markierung eines Knotens int getmark () { return this. mark ; // Liefert die Referenz auf den linken Kindknoten TreeNode getleft () { return this. left ; // Liefert die Referenz auf den rechten Kindknoten TreeNode getright () { return this. right ;... // Repraesentiert einen markierten Binaerbaum public class BinTree { private TreeNode root ;... Die Implementierung des Baums hält dann die Referenz auf den Wurzelknoten root. Von diesem Wurzelknoten sind alle anderen Knoten des Baumes erreichbar. Jeder Knoten des Baumes ist selbst wiederum Wurzelknoten des von ihm aufgespannten Unterbaums. Bäume sind - wie auch Listen - rekursive Datentypen. Bei rekursiven Datentypen wird der Datentyp selbst zu seiner eigenen Definition herangezogen. Eine Liste ist entweder leer oder besteht aus einem Element und einer (Rest-) Liste. Eine Baum ist entweder leer oder besteht aus einer Markierung und einem linken und rechten (Unter-) Baum. Bei einer Definition einer rekursiven Datenstruktur gibt es einen oder mehrere Basisfälle (z.b. leere Liste / leerer Baum) und Rekursionsfälle, die beschreiben, wie man aus kleineren Instanzen der Datenstruktur größere aufbaut. In der Implementierung in Java, die wir hier präsentieren, zeigt sich der rekursive Charakter von Bäumen dadurch, dass die Baumknoten als Attribute Referenzen auf Baumknoten haben. 1 1 In Java können Referenzen auf beliebige Objekt des zugehörigen Referenztyps verweisen. Dies kann 3

Berechnungen auf rekursiven Datenstrukturen wie Bäumen und Listen lassen sich in der Regel auf eine Kombination der Berechnung für den aktuellen Knoten und des Ergebnisses der Berechnung für den rekursiven Teil der Datenstruktur zurückführen. Essentiell ist es dabei, die Berechnung für die Basisfälle nicht zu vergessen. Um z.b. alle Markierungen in einem mit int-markierten Binärbaum aufzusummieren, addieren wir zu der Markierung des Knotens, der diesen Baum aufspannt, die Summe der Markierungen für den linken und für den rechten Unterbaum. Dazu berechnen wir die Summe zum Unterbaum des linken Kindknoten, dann zum rechten Kindknoten und addieren die jeweiligen Ergebnisse dann zur Markierung des aktuellen Knotens. Im Basisfall für den leeren Baum (Knoten ist gleich null) ist die Summe gleich 0. public class BinTree { private TreeNode root ;... // berechnet die Summe der Markierungen des Baums public int sum () { return sum ( this. root ); private int sum ( TreeNode node ) { if ( node == null ) { return 0; return node. getmark () + sum ( node. getleft ()) + sum ( node. getright ()); Dabei verwenden wir die gleiche Methode sum() für die Summation bei den Kindknoten. Die Methode sum() ist rekursiv, sie ruft sich selbst erneut auf. Die rekursive Struktur des Baums spiegelt sich wieder in diesen rekursiven Methodenaufrufen. Frage 2: enthält? Wie können wir testen, ob der Baum eine bestimmte Markierung Was ist das Ergebnis im Basisfall (leerer Baum)? Was ist das Ergebnis im Rekursionsfall? Schreiben Sie eine Methode public boolean contains(int x) für die Klasse BinTree, die true liefert, wenn einer der Knoten des Baumes mit x markiert ist! Mehr zur Rekursion erfahren Sie in Abschnitt 2. zu unerwünschten zirkulären Objektgeflechten führen, die der obigen Definition von rekursiven Datentypen widersprechen. Wir gehen hier davon aus, dass innerhalb der hierbeschriebenen rekursiven Datentypen unterschiedliche Referenzen nicht auf das gleiche Objekt verweisen. 4

1.2 Sortierte markierte Bäume Definition Ein mit ganzen Zahlen markierter Binärbaum heißt sortiert, wenn für alle Knoten k gilt: Alle Markierungen der linken Nachkommen von k sind kleiner als oder gleich m(k). Alle Markierungen der rechten Nachkommen von k sind größer als m(k). Frage 3: Ist dieser markierte Binärbaum sortiert? :BinTree root 5 2 1 4 10 3 15 Die Sortiertheitseigenschaft von sortierten markierten Binärbaum erleichtert es ungemein, Elemente in dem Baum zu suchen. Sie werden daher auch oft Suchbäume genannt. Wir beginnen wieder mit dem root-knoten: In einem leeren Baum ist das Element nicht enthalten. Falls der aktuelle Knoten mit dem gesuchten Element markiert ist, ist es im Baum enthalten. Andernfalls suchen wir rekursiv beim linken Kindknoten, falls das gesuchte Element kleiner ist, als die Markierung des Knotens; ist das gesuchte Element größer, suchen wir rekursiv beim rechten Kindknoten. // Repraesentiert einen sortierten markierten Binaerbaum public class SortedBinTree { private TreeNode root ; public SortedBinTree () { root = null ; // prueft, ob ein Element im Baum enthalten ist public boolean contains ( int element ) { return contains ( root, element ); private boolean contains ( TreeNode node, int element ) { if ( node == null ) { 5

return false ; if ( element < node. getmark ()) { // kleinere Elemente links suchen return contains ( node. getleft (), element ); else if ( element > node. getmark ()) { // groessere Elemente rechts suchen return contains ( node. getright (), element ); else { // gefunden! return true ; Neue Knoten werden immer als Blätter eingefügt. Die Position des Blattes wird durch den Wert des neuen Eintrags festgelegt. Um Elemente in einen SortedBinTree einzufügen wählen wir das folgende algorithmische Vorgehen: Ist der Baum noch leer, wird der erste Eintrag als Wurzel des Baums hinzugfügt. Ein Knoten wird in den linken Unterbaum der Wurzel eingefügt, wenn sein Wert kleiner gleich ist als der Wert der Wurzel; in den rechten, wenn er größer ist. Dieses Verfahren wird rekursiv fortgesetzt, bis die Einfügeposition bestimmt ist. Beispiel: Beim Einfügen von 33 in den skizzierten Baum wird - angefangen bei der Wurzel - die Markierung des Knotens mit 33 verglichen. 33 ist kleiner als 45; daher wird das Einfügen rekursiv auf dem linken Kindknoten fortgeführt; 33 ist größer als 22; daher wird das Einfügen rekursiv auf dem rechten Kindknoten fortgeführt; 33 ist kleiner als 42; da der Knoten mit Markierung 42 keinen linken Kindknoten hat, wird ein neuer Knoten mit Markierung 33 als linker Kindknoten hier eingefügt. 45 22 57 17 42 52 65 33 49 6

Die Implementierung folgt diesem rekursiven Schema: public void add ( int element ) { root = add ( root, element ); private TreeNode add ( TreeNode node, int element ){ if ( node == null ) { return new TreeNode ( element ); else if ( element <= node. getmark ()) { node. setleft ( add ( node. getleft (), element )); else { node. setright ( add ( node. getright (), element )); return node ; Bemerkungen Die Reihenfolge des Einfügens bestimmt das Aussehen des sortierten markierten Binärbaums. Die folgenden Bäume sind durch die Einfügereihenfolge 2 3 1 bzw. 1 3 2 bzw. 1 2 3 enstanden. 2 1 1 1 3 3 2 2 3 Wie man bei dem letzten der drei Beispiel sieht, entartet der Baum bei sortierter Einfügereihenfolge zur linearen Liste. 2 Rekursion Rekursion ist eine elegante Strategie zur Problemlösung, die es erlaubt eine Problemstellung auf ein ähnliche, aber kleinere Problemstellung zurückzuführen. Um Rekursion genauer zu charakterisieren, führen wir hier einige Begriffe ein. Diese beziehen sich auf Methoden, sind aber analog auf Datenstrukturen, Prozeduren etc. anwendbar. Definition Eine Methodendeklaration m heißt direkt rekursiv, wenn der Methodenrumpf einen Aufruf von m enthält. Eine Menge von Methodendeklarationen heißen verschränkt rekursiv oder indirekt rekursiv (engl. mutually recursive), wenn die Deklarationen gegenseitig voneinander abhängen. Eine Methodendeklaration heißt rekursiv, wenn sie direkt rekursiv ist oder Element einer Menge verschränkt rekursiver Methoden ist. 7

Wie bereits im vorherigen Abschnitt gesehen, tritt Rekursion in natürlicher Weise auf Datenstrukturen wie Listen und Bäumen auf. Wir können auch aber auch rekursive Methoden verwenden, um Berechnungen auf Zahlen durchzuführen. Das klassiche Beispiel für eine rekursive Prozedur ist die Berechnung der Fakultätsfunktion, die das Produkt der ersten n natürlichen Zahlen berechnet. { n (n 1)! falls n > 0 n! = 1 falls n = 0 public static int fac ( int n) { if (n == 0) { return 1; else { return n * fac (n -1) ; Der Basisfall liefert einen Wert ohne einen nachfolgenden rekursiven Aufruf. Der Rekursionsschritt verwendet das Ergebnis von rekursiven Methodenaufrufen für (andere) Parameterwerte für die Berechnung im aktuellen Methodenaufruf. Wichtig: Der Basisfall muss nach endlich vielen rekursiven Aufrufen erreicht werden, damit das Programm terminiert. Dies ist in dieser Implementierung nur für Parameter n 0 der Fall 2. Eine rekursive Methodendeklaration m heißt linear rekursiv, wenn in jedem Ausführungszweig höchstens ein Aufruf von m auftritt. Die Methode zur Implementierung der Fakultätsfunktion ist ein typisches Beispiel für eine linear rekursive Methode. Kaskadenartige Rekursion Die Fibonacci-Folge ist eine Folge von natürlichen Zahlen, die bei diversen Naturphänomen in Erscheinung tritt (z.b. zur Modellierung von Hasenpopulationen). Jede Fibonacci-Zahl ist die Summe ihrer beiden Vorgänger: 0 1 1 2 3 5 8 13 21 34 55 89 144 233... 0 für n = 0 fib(n) = 1 für n = 1 fib(n 1) + fib(n 2) für n 2 public static int fib ( int n) { if (n == 0) { return 0; if (n == 1) { 2 Wir lassen in diesem Fall Überläufe außer Acht. 8

return 1; return fib (n -2) + fib (n -1) ; Eine rekursive Methodendeklaration m, die nicht linear rekursiv ist, heißt kaskadenartig rekursiv. Das heißt, es muss in mindestens einem Ausführungszweig der Methode mehr als ein Aufruf von m auftreten. Repetitive Rekursion Eine linear rekursive Methodendeklaration m heißt repetitiv rekursiv (auch endrekursiv, engl. tail recursive), wenn jeder Aufruf von m in m der letzte auszuwertende Ausdruck ist. Die Prozedur boolean contains(treenode node, int element) aus Abschnitt 1.2 ist repetitiv-rekursiv, da der Aufruf von contains() der jeweils letzte auszuwertende (Teil-) Ausdruck ist. Die Prozedur fib() ist hingegen nicht repetitiv rekursiv: Hier ist der letzte auszuwertende Ausdruck die Addition der Rückgabewerte der rekursiven Aufrufe. Frage 4: Auch die Prozedur zur Berechnung der Fakultätsfunktion ist in der obigen Implementierung nicht repetitiv-rekursiv. Wie kann man sie umschreiben, um eine repetitiv-rekursive Variante zu erhalten. Die Ausführung von repetitiv-rekursive Methoden ist in einigen Programmiersprachen sehr effizient umgesetzt (z.b. Haskell) und dort von besonderer Bedeutung. Verschränkte Rekursion Zuletzt nochmal ein Beispiel zur verschränkten Rekursion. Das folgende Methodenpaar testet, ob eine (positive) Zahl gerade bzw. ungerade ist. public static boolean istgerade ( int n) { if (n == 0) { return true ; else { return istungerade (n -1) ; public static boolean istungerade ( int n){ if (n == 0) { return false ; else { return istgerade (n -1) ; 2.1 Zur Terminierung von rekursiven Funktionen Angenommen, wir wollen die Fakultät von 3 berechnen. Dabei wird folgender Prozeduraufrufbaum erzeugt: 9

fac(3) fac(2) fac(1) fac(0) Was geschieht aber beim Aufruf von fac(-2)? Diese Ausführung liefert folgenden Prozeduraufrufbaum: fac(-2) fac(-3) fac(-4) fac(-5)... Die Ausführung für den Parameterwert -2 liefert eine unendliche Folge von rekursiven Aufrufen von fac(): Die Ausführung terminiert nicht 3, da der Basisfall mit Parameterwert 0 nicht erreicht wird. Es lässt sich dabei folgende Beobachtung machen: Der Abstand der Parameterwerte zum Basisfall wird beim Aufruf mit Parameterwert 3 mit jedem Aufruf kleiner, beim Aufruf mit Parameterwert -2 jedoch mit jedem Aufruf größer. Die Ideen den Abstand zum Basisfall zu messen kann man verwenden, um zu beweisen, dass eine Methode terminiert. Dazu definiert man eine sogenannte Abstiegsfunktion h, welche den Abstand der Parameterwerte zu einem Basisfall misst. Dazu muss h jeder gültigen Kombination von Parameterwerten eine natürliche Zahl (N 0 ) zuweisen. Es sollte dann gelten, dass der Wert von h bei jedem rekursiven Aufruf für die neuen Parameterwerte echt kleiner ist als für die Parameterwerte des aktuellen Aufrufs. Da in N 0 jede monoton fallende Folge endlich ist, ist eine unendliche Folge von rekursiven Aufrufen durch die Voraussetzungen ausgeschlossen. Wenn andere Ursachen zur Nichtterminierung (zum Beispiel Schleifen) ausgeschlossen werden können, dann ist damit die Terminierung der rekursiven Methode gezeigt. int f( int x, int y) { if (x >= y) { return x; else { return f (2*x, y); Die Methode f(x, y) berechnet die kleinste Zahl z y, so dass z = x 2 a für ein a N 0. Wir wollen zeigen, dass die Methode für Werte x > 0 terminiert. Die Idee ist, dass dann x in jedem rekursiven Aufruf größer wird und somit näher an den Basisfall x >= y kommt. Dazu definieren wir die Abstiegsfunktion h(x, y) = max(0, y x). Wir 3 In Java benötigen Methodenaufrufe Speicherplatz, so dass in der Praxis nach endlich vielen Aufrufen der Speicher voll ist und der Fehler StackOverflowError ausgelöst wird. 10

verwenden hier das Maximum mit 0 um auszudrücken, dass der Basisfall für x y bereits erreicht ist. Für den rekursiven Aufruf müssen wir jetzt zeigen, dass h(2 x, y) < h(x, y) gilt, also dass die neuen Parameterwerte einen kleineren Wert der Abstiegsfunktion haben. Dies ist hier der Fall, da 2 x mit den gewählten Voraussetzungen immer größer als x ist 4. Dieses Nachweisverfahren zur Terminierung ist nicht auf Methoden mit Zahlen beschränkt. Um die Terminierung von rekursiven Methoden auf Listen beispielsweise nachzuweisen, kann man die Länge der Liste zur Konstruktion von h zur Hilfe nehmen. 2.2 Rekursion vs. Iteration Für jede Methode, die ein Problem mit Hilfe von Rekursion löst, kann eine Alternativimplementierung gefunden werden, die das gleiche Problem iterativ, d.h. mit Schleifenkonstrukten löst. public static int fac ( int n) { if (n == 0) { return 1; else { return n * fac (n -1) ; class IntList { private Node first ;... public int sumentries () { return sumentries ( this. first ); private int sumentries ( Node n) { if (n == null ) { return 0; else { return n. getvalue () + sumentries (n. getnext ()); public static int fac ( int n) { int result = 1; int i = n; while (i!= 0) { result = i * result ; i - -; return result ; class IntList { private Node first ;... public int sumentries () { int result = 0; Node n = this. first ; while (n!= null ) { result += n. getvalue (); n = n. getnext (); return result ; Die rekursive Variante führt allerdings oft zu einfacherem und eleganterem Code, wenn man Probleme auf rekursiven Datenstrukturen löst. Manche Programmiersprachen (insbesondere funktionale Sprachen wie Haskell, Ocaml, Erlang etc.) bieten keine Sprachmittel für Schleifen an. In diesen Sprachen müssen rekursive Funktionen immer dann verwendet werden, wenn die Anzahl der Operationen 4 Streng genommen sollten wir noch y einschränken, zum Beispiel y < 2 30, um Überläufe ausschließen zu können. 11

nicht von vorneherein beschränkt ist 5. Andererseits gibt es einige imperative Sprachen (OpenCL, diverse C Compiler für Mikrocontroller, ältere Versionen von Fortran), die wiederum keine Rekursion als Sprachmittel zur Verfügung stellen. Programmierer müssen daher beide Varianten verstehen und anwenden können. 5 Oft verwendet man in diesen Sprachen auch Funktionen höherer Ordnung wie map, filter und fold, welche von bestimmten rekursiven Anwendungsfällen abstrahieren. 12

Hinweise zu den Fragen Hinweise zu Frage 1: Jeder Knoten kann hier als Wurzelknoten betrachtet werden. Dementsprechend ergibt sich die Antwort auf die anderen beiden Teilfragen. Hinweise zu Frage 2: Im Basisfall (leerer Baum n == null) kann x nicht enthalten sein. Daher wird hier false zurückgegeben. Andernfalls testen wir, ob der aktuelle Knoten selbst die Markierung hat oder ob sich die Markierung im Unterbaum von seinem linken bzw. rechten Kindknoten findet. class BinTree {... public boolean contains ( int x) { return contains (x, this. root ); private boolean contains ( int x, TreeNode n){ if (n == null ) { return false ; else { return n. getmark () == x contains (x, n. getleft ()) contains (x, n. getright ()); Hinweise zu Frage 3: Nein, rechts vom Knoten mit Markierung 5 gibt es einen Knoten mit Markierung 3. Hinweise zu Frage 4: public static int fac ( int n) { return helperfac (n, 1); public static int helperfac ( int n, int x) { if (n == 0) { return x; else { return helperfac (n -1, n * x); 13