Übungen zu Algorithmen - Universität Osnabrück

Werbung
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
Herunterladen