Institut für Informatik Prof. Dr. Oliver Vornberger Lukas Kalbertodt, B.Sc. Nils Haldenwang, M.Sc. Universität Osnabrück, 17.01.2017 http://www-lehre.inf.uos.de/~ainf Testat bis 25.01.2017, 14:00 Uhr Übungen zu Algorithmen Wintersemester 2016/2017 Blatt 11: Bäume Aufgabe 11.1: Fragen (30 Punkte) Beantworten Sie Ihrem Tutor bzw. Ihrer Tutorin Fragen zur Vorlesung. Aufgabe 11.2: MinMaxListe (30 Punkte) Vervollständigen Sie die im Anhang befindliche Klasse MinMaxListe. Eine MinMaxListe ist eine Unterklasse der VerweisListe und erweitert diese um die Funktionalität zu jeder Zeit auf das größte und kleinste Element der Liste zugreifen zu können. Dafür gibt es die zusätzlichen Methoden Object getMin() und Object getMax(). Implementieren Sie die Funktionalität möglichst effizient durch geeignetes Überschreiben der relevanten Methoden aus Liste, die Oberklasse VerweisListe darf dabei nicht verändert werden. Insbesondere darf auch deren Funktionalität nicht geändert werden, geben Sie acht auf die korrekte Beibehaltung des aktuellen Elements. Sie dürfen annehmen, dass weder doppelte Elemente, noch null eingefügt werden. Außerdem dürfen Sie annehmen, dass alle eingefügten Objekte das Interface Comparable implementieren. Testen Sie Ihre Implementierung mit der im Anhang befindlichen Testklasse MinMaxTest. Musterlösung: /******************************* MinMaxListe.java *****************************/ import AlgoTools.IO; /** */ public class MinMaxListeLoesung extends VerweisListe { // Kleinstes und größtes Objekt, null wenn Liste leer. private Object min; private Object max; /** * Gibt das kleinste Element der Liste zurück * @return Das kleinste Element */ public Object getMin() { return min; } /** * Gibt das größte Element der Liste zurück * @return Das größte Element */ public Object getMax() { return max; } public void insert(Object x) { // Objekt einfügen super.insert(x); Comparable compX = (Comparable)x; // Falls das eingefügte Objekt größer oder kleiner als Min/Max ist, // muss man Min/Max aktualisieren. Wenn min und max null sind, wird // es auf jeden Fall überschrieben. if(min == null || compX.compareTo(min) < 0) { min = compX; } if(max == null || compX.compareTo(max) > 0) { max = compX; } } /** * Durchsucht die ganze Liste nach dem Minimum und Maximum */ private void findMinMax() { // Wir speichern uns unsere jetzige Position, um dort später wieder // hinzukommen. Wenn wir derzeit in der endpos sind, speichern wir // uns null. Object position = endpos() ? null : elem(); // An den Anfang der Liste springen reset(); // Wir tun so als wäre das jetzige (erste) Element das Min/Max min = elem(); max = elem(); // Einmal die Liste von vorne nach hinten durchgehen while(!endpos()) { 2 // Wenn das jetzige Element kleiner oder größer ist, als unser // bisheriges Min/Max, aktualisieren wir unser Min/Max Comparable e = (Comparable)elem(); if(e.compareTo(min) < 0) { min = e; } if(e.compareTo(max) > 0) { max = e; } // In der Liste weitergehen advance(); } // Alte Position der Liste wiederherstellen reset(); while(!endpos() && elem() != position) { advance(); } } public void delete() { Object e = elem(); super.delete(); if(empty()) { // Liste ist leer, also sind min und max = null min = null; max = null; } else if(e == min || e == max) { // Das Maximum oder Minimum wurde gelöscht // -> Ganze Liste durchlaufen, um das neue zu finden findMinMax(); } } } Aufgabe 11.3: Inorder-/Postorder-Bau (10 Punkte) Gegeben sei das Ergebnis der Inorder-Traversierung und der Postorder-Traversierung eines Baumes: Inorder: Postorder: 42 23 73 37 297 314 300 23 42 297 300 314 37 73 Durch diese beiden Traversierungen ist ein eindeutiger Baum definiert. Zeichnen Sie diesen Baum 3 und erklären Sie Ihrem Tutor, wie Sie den Baum aus den Traversierungen erstellt haben. Machen Sie die Zwischenschritte geeignet deutlich. Musterlösung: 73 42 37 23 314 297 300 Aufgabe 11.4: BaumTools (30 Punkte) Ergänzen Sie die im Anhang befindliche Klasse BaumTools um die folgenden Methoden: public static int baumTiefe(Baum b), die für den übergebenen Baum dessen Tiefe beziehungsweise Höhe zurückliefert. public static VerweisBaum inorderPostorderBau(int[] inorder, int[] postorder), die analog zu Aufgabe 3 aus einer gegebenen Inorder- und einer PostorderTraversierung eines Baumes einen neuen VerweisBaum aufbaut und zurückliefert. Hinweis: Sie können davon ausgehen, dass die beiden Traversierungen korrekt sind und kein Element doppelt vorhanden ist. public static int anzahlKnoten(Baum b), die für den übergebenen Baum die Anzahl der Knoten zurückliefert. Hinweis: Leere Teilbäume zählen nicht als Knoten. public static boolean istVollstaendig(Baum b), die für den übergebenen Baum true liefert, wenn dieser vollständig ist und false sonst. Die Klasse BaumTools.java enthält bereits einige Methoden zur Ausgabe eines Baumes auf der Konsole, die Sie zur Visualisierung Ihrer Zwischenergebnisse nutzen können. Die Methode baumTiefe muss dafür allerdings bereits korrekt implementiert sein. Testen Sie Ihre BaumTools mit der im Anhang befindlichen Klasse BaumToolsTest.java. Es wird empfohlen, noch nicht implementierte Tests zunächst auszukommentieren, um alle Methoden Schritt für Schritt testen zu können. Musterlösung: /****************************** BaumTools.java ******************************/ import AlgoTools.IO; /** * Utility-Klasse mit einigen Algorithemn fuer Baeume * */ public class BaumToolsLoesung { /** 4 * Liefert die Anzahl der Knoten eines Baumes * @param b der betreffende Baum * @return Anzahl der Knoten */ public static int anzahlKnoten(Baum b) { if(b.empty()) return 0; else return 1 + anzahlKnoten(b.left()) + anzahlKnoten(b.right()); } /** * Erzeugt aus einer gegebenen Inorder- und Postorder-Traversierung * einen neuen VerweisBaum. Die Traversierungen muessen korrekt sein, * und genau einen Baum eindeutig beschreiben. Sonst ist das Verhalten * nicht definiert, es wird ein fehlerhafter Baum zurueck geliefert oder * eine Runtime- bzw. ArrayIndexOutOfBounds-Exception wird geworfen. * @param inorder Das Array mit der Inorder-Traversierung * @param postorder Das Array mit der Postorder-Traversierung * @return der erzeugte VerweisBaum */ public static VerweisBaum inorderPostorderBau(int[] inorder, int[] postorder) { //Fertig oder Fehler, gebe leeren Baum zurueck if(inorder.length == 0 || postorder.length == 0) return new VerweisBaum(); //Besorge Wurzel aus Postorder int wurzel = postorder[postorder.length - 1]; //Suche in Inorder int i = -1; while(inorder[++i] != wurzel); if(inorder[i] != wurzel) throw new RuntimeException("Fehler: Baum kann nicht erstellt werden!"); //Teilbaeume int[] newInorderLinks = new int[i]; int[] newInorderRechts = new int[inorder.length - i - 1]; int[] newPostorderLinks = new int[i]; int[] newPostorderRechts = new int[inorder.length - i - 1]; //Kopiere linken Teilbaum for(int j = 0; j < i; j++) { 5 newInorderLinks[j] = inorder[j]; newPostorderLinks[j] = postorder[j]; } //Kopiere rechten Teilbaum for(int j = i + 1; j < inorder.length; j++) { newInorderRechts[j - (i + 1)] = inorder[j]; newPostorderRechts[j - (i + 1)] = postorder[i + (j - (i + 1))]; } //Erstelle Rekursiv den Baum return new VerweisBaum(inorderPostorderBau(newInorderLinks,newPostorderLinks), wurzel, inorderPostorderBau(newInorderRechts, newPostorderRechts)); } /** * Liefert die Tiefe eines Baumes (rekursiv) * @param b Baum, dessen Tiefe bestimmt werden soll * @return die Tiefe des Baumes, ein leerer Teilbaum hat die Tiefe 0 */ public static int baumTiefe(Baum b) { if(b.empty()) return 0; else { int links = baumTiefe(b.left()); int rechts = baumTiefe(b.right()); //Liefere das Maximum if(links >= rechts) { return 1 + links; } else { return 1 + rechts; } } } /** * Liefert true, wenn der übergebene Baum vollständig ist. * * @param b der zu prüfende Baum */ public static boolean istVollstaendig(Baum b){ if(b.empty()) return true; 6 return (baumTiefe(b.left()) == baumTiefe(b.right())) && istVollstaendig(b.left } /** * Druckt einen Baum auf der Konsole ebenenweise aus. * Nutzt baumTiefe(Baum), printEbene(Baum,int,int) und spaces(int). * @param b der zu druckende Baum */ public static void printBaum(Baum b) { //Berechne die Tiefe des Baumes int resttiefe = baumTiefe(b); //Gehe die Ebenen des Baumes durch for(int i = 0; i < resttiefe; i++) { //Drucke die Ebene, beruecksichtige Anzahl der Leerzeichen //fuer korrekte Einrueckung printEbene(b, i, spaces(resttiefe - i)); IO.println(); IO.println(); } } /** * Druckt eine Ebene eines Baumes aehnlich der Breitensuche * aus. 0 ist dabei die Wurzel. Vor und nach jedem Element * werden spaces Leerzeichen eingefuegt. * @param b der auszugebende Baum * @param ebene die gewuenschte Ebene beginnend bei 0 * @param spaces Anzahl von Leerzeichen vor und nach jedem Element */ public static void printEbene(Baum b, int ebene, int spaces) { //Wenn 0 erreicht, drucke Element mit Leerzeichen if(ebene == 0) { IO.print("", spaces); if(b != null) { IO.print(b.value()); } else { //Wenn Nullzeiger uebergeben wurde, ein Leerzeichen drucken IO.print(" "); } IO.print(" ", spaces + 1); 7 } else { //Steige rekursiv ab, betrachte Soehne als Teilbaeume und verringere //dabei die zu druckende Ebene. In endende Aeste wird mit dem Nullzeiger //gelaufen. if(b != null && !b.left().empty()) { printEbene(b.left(), ebene - 1, spaces); } else { printEbene(null, ebene - 1, spaces); } if(b != null && !b.right().empty()) { printEbene(b.right(), ebene - 1, spaces); } else { printEbene(null, ebene - 1, spaces); } } } /** * Berechnet die Anzahl an benoetigten Leerzeichen fuer * eine korrekte Einrueckung, also 0.5 * (2^(ebene) - 2). * @param ebene die Ebene, Blaetter sind Ebene 1, darueber aufsteigend * @return Anzahl der Leerzeichen zur Elementtrennung */ private static int spaces(int ebene) { if(ebene == 1) { return 0; } else { //verdoppele die Leerzeichen gegenueber der Ebene darunter //und fuege ein weiteres Leerzeichen hinzu return 2 * spaces(ebene - 1) + 1; } } } 8