Programmierung und Programmiersprachen Sommersemester 2006 Lehrstuhl für Angewandte Telematik / ee-Business Institut für Informatik Universität Leipzig Stiftungslehrstuhl der Deutschen Telekom AG [email protected] www.lpz-ebusiness.de +49 (341) 97 323 30 PuPS 08.05.2006 Übungsgruppen • Die Übungen zur Vorlesung beginnen in dieser Woche! • • • • • • • • • # Tag 1 Mo, 2 Mo, 3 Mi, 4 Mi, 5 Do, 6 Do, 7 Fr, 8 Fr, PuPS Woche Uhrzeit Raum gerade 09:15-10:45 SG 3-07 ungerade 09:15-10:45 SG 3-07 gerade 13:15-14:45 SG 3-97 ungerade 13:15-14:45 SG 3-97 gerade 11:15-12:45 SG 3-01 ungerade 11:15-12:45 SG 3-01 gerade 07:30-09:00 SG 3-11 ungerade 07:30-09:00 SG 3-11 Betreuer Vincent Wolff-Marting Vincent Wolff-Marting Tobias Brückmann Tobias Brückmann Ralf Laue Ralf Laue Matthias Book Matthias Book 2 1 Auf dem Weg zu HeapSort • Problem: Gegeben sei ein Feld a mit n Werten, die sortiert werden müssen. • Ausgangssituation: Werte im Feld sind unsortiert • Ziel: sortiertes Feld • bereits bekannte Algorithmen (aus DigInf): • BubbleSort, SelectionSort, InsertionSort, ShellSort, QuickSort, MergeSort, DistributionSort • nun betrachtet: HeapSort • arbeitet auf einem Feld, das einen vollständigen Baum enthält PuPS 3 Vollständiger Baum • informale Definition: • Ein binärer Baum heißt vollständiger Baum, wenn • mit Ausnahme der untersten Schicht alle Schichten voll besetzt sind • auf der untersten Schicht von links nach rechts gesehen bis zu einem bestimmten Punkt alle Knoten existieren PuPS 4 2 Vollständiger Baum im Feld • keine Speicherung von Zeigern notwendig - stattdessen: • verwende die Indices 1…n zur Nummerierung der Knoten • • • • Knoten 1 ist die Wurzel, für jeden anderen Knoten i > 1 gilt: i/2 ist der Vater (mit ganzzahliger Division) 2*i ist der linke Sohn (sofern er existiert) 2*i+1 ist der rechte Sohn (sofern er existiert) • Feldelement a[i] enthält dann Beschriftung von Knoten i 1 2 3 4 8 5 9 10 6 11 7 12 PuPS 5 Vorbereitung der Implementierung • Wir werden den HeapSort-Algorithmus im Rahmen der Klasse Heap implementieren. • Der Konstruktor für die Klasse Heap allokiert das Feld a. • Da a[0] vom Algorithmus nicht benutzt wird, benötigen wir ein Feld mit n+1 Elementen, um n Zahlen zu speichern. • Die Initialisierung der Feldelemente kann über die Methode setze erfolgen, das Auslesen über die Methode lese. • Außerdem brauchen wir später noch die Methode tausche zum Vertauschen des Inhalts zweier Feldelemente. PuPS 6 3 Klasse Heap class Heap { private int[] a; public Heap(int n) { a = new int[n+1]; } public void setze(int i, int x) { a[i] = x; } public int lese(int i) { return a[i]; } private void tausche(int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; } ... // Methoden heapify, baueHeap, sortiere } PuPS 7 Heap-Bedingung • Ein Feld genügt der Heap-Bedingung im Knoten i, falls im (vollständigen) Unterbaum mit Wurzel i jeder Knoten eine kleinere Beschriftung als seine Söhne hat • In Blättern ist die Heap-Bedingung trivialerweise immer erfüllt. • Im Beispiel ist die Heap-Bedingung in Knoten 3-11 erfüllt, • nicht jedoch im Knoten 2 • denn a[2] = 12, a[4] = 5 und a[5] = 9, aber 12 > 5 und 12 > 9 • und auch nicht im Knoten 1 • denn a[1] = 7 und a[3] = 0, aber 7 > 0. PuPS 8 4 Definition Heap / Konzept HeapSort • Definition: Das Feld a[1], …, a[n] ist ein Heap, falls die Heap-Bedingung im Knoten 1 erfüllt ist. • Hieraus ergibt sich unmittelbar als Konsequenz, dass das kleinste Element eines Heaps immer in der Wurzel, d.h. in a[1], steht. • Der Sortieralgorithmus HeapSort arbeitet in zwei Phasen: • Es ist ein Feld a mit n Elementen gegeben. • In der ersten Phase wird dieses Feld in einen Heap umgestaltet, d.h. es wird dafür gesorgt, dass der entsprechende binäre Baum die Heap-Bedingung im Knoten 1 erfüllt. • In der zweiten Phase wird aus dem zuvor aufgebauten Heap systematisch ein geordnetes Feld erzeugt. PuPS 9 Phase 1: Heap-Aufbau (lokal) • Idee: Sei in Knoten i die Heap-Bedingung nicht erfüllt (i ist also kein Blatt), wohl aber in allen Söhnen. • Dann werden folgende Vertauschungen vorgenommen, um die Heap-Bedingung in i zu erfüllen: • falls 2*i <= n, 2*i+1 > n (d.h. i hat nur einen Sohn): • vertausche a[i] mit a[2*i] • falls 2*i < n, 2*i+1 <= n (d.h. i hat zwei Söhne): • suche den Sohn k mit k ∈ {2*i, 2*i+1}, so dass gilt: a[k] = min {a[2*i], a[2*i+1]} • vertausche a[i] mit a[k] • wende diese Idee rekursiv auf k an PuPS 10 5 Heap-Bedingung in Knoten 2 verletzt Knoten 1 7 Knoten 2 12 Knoten 4 8 Knoten 3 Knoten 5 5 13 10 9 0 1 3 Knoten 6 Knoten 7 15 Knoten 8 Knoten 9 Knoten 10 Knoten 11 Heap-Bedingung im Teilbaum erfüllt PuPS 11 Herstellung der Heap-Bedingung • Die Heap-Bedingung kann im Knoten 2 wie folgt hergestellt werden: • Wir vertauschen die Beschriftung im Knoten 2 mit der Beschriftung desjenigen Sohnes, der die kleinere Beschriftung trägt. In diesem Fall handelt es sich um den Knoten 4. • Wir nehmen hierzu die kleinere der Beschriftungen der Söhne, damit im Knoten 2 die Heap-Bedingung lokal erfüllt ist: Die Beschriftung des Knotens 2 ist dann kleiner als die Beschriftungen beider seiner Söhne. PuPS 12 6 Herstellung der Heap-Bedingung • Nun ist zwar die Heap-Bedingung im Knoten 2 lokal hergestellt. Als Folge ist sie aber im Knoten 4 verletzt! • Die Vertauschung der Beschriftung eines Knotens mit der kleineren Beschriftung eines seiner Söhne lässt sich an dieser Stelle wiederholen, sodass die Beschriftung 12 in den Knoten 8 (ein Blatt) wandert. • Trivialerweise ist die Heap-Bedingung in einem Blatt erfüllt. • Durch das beschriebene Vorgehen haben wir also schrittweise dafür gesorgt, dass die Heap-Bedingung im Knoten 2 hergestellt wurde. PuPS 13 Methode heapify • Zur lokalen Herstellung der Heap-Bedingung dient die Methode heapify der Klasse Heap. Sie bekommt als Eingabe den Knoten dieserKnoten und die aktuelle heapGröße des Feldes. • Zunächst werden der linke und der rechte Sohn bestimmt. Danach findet eine Fallunterscheidung statt: • Fall 1: Der linke Sohn liegt noch im Feld, der rechte Sohn nicht mehr: In diesem Fall muss lediglich getestet werden, ob die Beschriftung dieserKnotens größer ist als die Beschriftung seines einzigen Sohnes. • Fall 2: Auch der rechte Sohn liegt noch im Feld: In diesem Fall muss der Sohn mit der kleineren Beschriftung identifiziert und die Beschriftung dieserKnotens mit der Beschriftung des kleineren Sohns verglichen werden. PuPS 14 7 Methode heapify • Falls der Vergleich ergibt, dass die Heap-Bedingung verletzt ist (d.h. der Sohn ist kleiner als dieserKnoten): • mit Hilfe der elementaren Methode tausche wird der Inhalt dieserKnotens mit dem seines Sohnes vertauscht • da nun die Heap-Bedingung im Unterbaum, dessen Wurzel der Sohnknoten ist, verletzt sein kann, wird heapify rekursiv mit dem Sohn und der Heapgröße erneut aufgerufen. PuPS 15 Heap-Bedingung in Knoten 1 verletzt Knoten 1 Knoten 2 Knoten 4 12 8 5 Knoten 3 Knoten 5 13 7 10 9 0 1 3 Knoten 6 Knoten 7 15 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 16 8 Phase 1: Heap-Aufbau (global) • Die Heap-Bedingung gilt im Beispiel immer noch nicht für den Knoten 1, da die Beschriftungen beider Söhne (Knoten 2 und 3) kleiner sind als die Beschriftung der Wurzel des Baumes. • Lösung: Um die Heap-Bedingung im ganzen Baum zu erfüllen, verwenden wir die Methode baueHeap, die rückwärtslaufend in jedem Knoten k = n/2, …, 1 die HeapBedingung durch Aufruf von heapify herstellt. • Knoten k > n/2 müssen wir nicht durchlaufen, da sie Blätter sind • Rückwärtslaufen nötig, um aus kleineren Heaps größere aufzubauen • Söhne sind immer schon Wurzeln von kleineren Heaps • zur lokalen Herstellung der Heap-Bedingung muss immer nur einer der Unterbäume modifiziert werden PuPS 17 Phase 2: Sortierung des Felds • Nachdem heapify durch baueHeap in der beschriebenen Art aufgerufen worden ist, stellt das Feld a einen Heap dar, der jetzt in der Methode sortiere dazu verwendet werden kann, das Feld zu sortieren. • Wir machen uns zunutze, dass in einem Heap das kleinste Element immer in der Wurzel liegt. Also nehmen wir es aus dem Heap heraus und legen es im sortierten Abschnitt des Felds ab. Dazu vertauschen wir einfach a[1] mit a[n]. • Um das zweitkleinste Element zu bestimmen, sorgen wir nun dafür, dass die restlichen n-1 Elemente wieder einen Heap bilden. Dazu nehmen wir die Methode heapify. • Anschließend können wir die Wurzel des neuen Heaps und a[n-1] miteinander vertauschen und haben so das nächste Element des Feldes sortiert. PuPS 18 9 Phase 2: Sortierung des Felds • In jedem Schritt schrumpft der Heap also um ein Element. • In den so freiwerdenden Zellen im Feld legen wir die bereits sortierten Elemente ab. • Das Feld ist vollständig sortiert, wenn der im nächsten Sortierschritt noch zu betrachtende Heap nur noch aus der Wurzel besteht. • Im Feld stehen dann alle Elemente in absteigender Reihenfolge. PuPS 19 HeapSort, Phase 2: 1. Sortierschritt Aufgebauter Heap 1 4 Austausch erster Sortierschritt 6 8 4 9 12 7 9 Reorganisation des Heaps (ohne 7 1 bereits sortiert 8 9 12 4 Letzten Knoten) PuPS 6 8 6 12 7 1 20 10 HeapSort, Phase 2: 2.+3. Sortierschritt Zweiter Sortierschritt 7 8 9 Reorganisation des Heaps (ohne die letzten beiden Knoten) 6 12 4 1 6 8 7 bereits sortiert 9 Dritter Sortierschritt 8 9 12 4 1 12 7 6 4 1 PuPS 21 Methode sortiere • Der Aufbau eines ersten Heaps aus einem gegebenen Feld erfolgt durch den Aufruf der Methode baueHeap. • Da jedes Blatt des Baumes für sich bereits einen Heap bildet, beginnen wir in der Mitte des Feldes mit dem Aufruf von heapify und bauen so immer größere Heaps auf. • Anschließend führt die Methode sortiere das bereits vorgestellte Vorgehen mit Vertauschen und Reorganisieren durch. PuPS 22 11 Aufruf von sortiere() Knoten 1 Knoten 2 Knoten 4 5 13 12 Knoten 3 Knoten 5 8 0 9 10 1 7 3 Knoten 6 Knoten 7 15 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 23 Aufruf von heapify(1,10) • heapify(1,10) • heapify(3,10) • heapify(7,10) Knoten 2 Knoten 4 12 Knoten 1 5 Knoten 3 Knoten 5 8 13 1 10 3 7 15 Knoten 6 Knoten 7 9 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 24 12 Aufruf von heapify(1,9) • heapify(1,9) • heapify(3,9) • heapify(6,9) Knoten 2 Knoten 4 Knoten 1 5 13 12 Knoten 3 Knoten 5 8 10 7 15 Knoten 6 Knoten 7 9 1 3 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 25 Aufruf von heapify(1,8) • heapify(1,8) • heapify(2,8) • heapify(4,8) Knoten 2 Knoten 4 12 Knoten 1 5 Knoten 3 Knoten 5 8 3 13 1 7 10 15 Knoten 6 Knoten 7 9 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 26 13 Aufruf von heapify(1,7) • heapify(1,7) • heapify(3,7) • heapify(6,7) Knoten 2 Knoten 4 12 Knoten 1 8 Knoten 3 Knoten 5 3 5 13 10 15 Knoten 6 Knoten 7 9 1 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 27 Aufruf von heapify(1,6) • heapify(1,6) • heapify(2,6) • heapify(5,6) Knoten 2 Knoten 4 12 5 Knoten 1 8 Knoten 3 10 Knoten 5 3 15 1 13 7 Knoten 6 Knoten 7 9 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 28 14 Aufruf von heapify(1,5) • heapify(1,5) • heapify(2,5) • heapify(4,5) Knoten 2 Knoten 4 12 Knoten 1 9 Knoten 3 10 Knoten 5 3 5 13 15 1 8 7 Knoten 6 Knoten 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 29 Aufruf von heapify(1,4) • heapify(1,4) • heapify(3,4) Knoten 1 15 Knoten 2 12 Knoten 4 13 5 Knoten 3 10 Knoten 5 3 1 9 8 7 Knoten 6 Knoten 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 30 15 Aufruf von heapify(1,3) • heapify(1,3) • heapify(2,3) Knoten 1 13 Knoten 2 12 Knoten 4 10 Knoten 5 3 5 Knoten 3 15 9 1 8 7 Knoten 6 Knoten 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 31 Aufruf von heapify(1,2) • heapify(1,2) • heapify(2,2) Knoten 1 15 Knoten 2 13 Knoten 4 10 5 Knoten 3 12 Knoten 5 3 1 9 8 7 Knoten 6 Knoten 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 32 16 Aufruf von heapify(1,1) Fertig! Alle Elemente sind in absteigender Reihenfolge sortiert. Knoten 1 15 Knoten 2 13 Knoten 4 10 5 Knoten 3 12 Knoten 5 3 1 9 8 7 Knoten 6 Knoten 7 0 Knoten 8 Knoten 9 Knoten 10 Knoten 11 PuPS 33 Binäre Suchbäume • Grundidee: Ein vorgelegtes Element x wird in einer geordneten Menge gesucht, es wird rekursiv vorgegangen. • Man beschaffe sich das mittlere Element der geordneten Menge und vergleiche es mit dem vorgelegten Element x. Stimmt das mittlere Element mit x überein, so ist man fertig; ist x kleiner, so wende man diese Idee auf alle Elemente an, die kleiner als das mittlere Element sind; ist x größer, wende man die Idee auf alle Elemente an, die größer als das mittlere Element sind. So kommt man durch fortgesetztes Halbieren sehr schnell zu der Entscheidung, ob x in der vorgegebenen Menge liegt oder nicht. PuPS 34 17 Binäre Suchbäume Definition: Sei B ein binärer Baum, dessen Knoten mit ganzen Zahlen beschriftet sind. B heißt binärer Suchbaum, falls gilt: • B ist leer oder • der linke und der rechte Unterbaum von B sind binäre Suchbäume, • ist w die Beschriftung der Wurzel, so sind alle Elemente im linken Unterbaum kleiner als w, alle Elemente im rechten Unterbaum größer als w. PuPS 35 Binäre Suchbäume - Beispiel 16 10 18 14 9 13 PuPS 24 15 36 18 Binäre Suchbäume • Der Aufbau eines binären Suchbaums erfolgt durch wiederholtes Einfügen in einen leeren Baum. • Die Reihenfolge der Werte, die in einen binären Suchbaum eingefügt werden, bestimmt die Gestalt des Baumes. • Eine Menge von Werten kann bei unterschiedlichen Eingabereihenfolgen zu verschiedenen Repräsentationen als Baum führen. PuPS 37 Binäre Suchbäume • Beispiele: 3 1 2 2 Eingabefolge 1 2 3 1 3 Eingabefolge 3 2 1 3 1 Eingabefolge 3 1 2 PuPS 2 1 2 3 Eingabefolge 2 1 3 oder 2 3 1 38 19 Übungsaufgaben 2.1, 2.2 • In der Vorlesung wurde die Implementierung eines Binärbaums in der Java-Klasse BinaerBaum vorgestellt. Implementieren Sie darin noch eine Methode int anzahlKnoten(), die alle Knoten im Baum zählt. • Formen Sie den folgenden Binärbaum zu einem Heap um. Schreiben Sie dazu alle notwendigen Operationen auf und zeichnen Sie das Endergebnis. E 1 D A 2 3 I G L C 4 5 6 7 K J F H B 8 9 10 11 12 PuPS 39 Übungsaufgaben 2.3, 2.4 • Der in der Vorlesung vorgestellte HeapSort-Algorithmus sortiert die Feldelemente in absteigender Reihenfolge. Wie muss der Algorithmus verändert werden, um die Feldelemente aufsteigend zu sortieren? • Erläutern Sie, wie eine Wertefolge beschaffen sein muss, die beim Einfügen in einen leeren binären Suchbaum dazu führt, dass der Baum zu einer Liste entartet. PuPS 40 20 Übungsaufgabe 2.5 • In der Vorlesung wurde die Implementierung eines binären Suchbaums in der Java-Klasse BST vorgestellt. Ergänzen Sie in der zugehörigen Klasse Knoten eine Methode void entferne(), die den betreffenden Knoten entfernt, dabei aber sicherstellt, dass der Gesamtbaum ein binärer Suchbaum bleibt. PuPS 41 Organisatorisches • Ergebnisse online unter www.lpz-ebusiness.de • Rückgabe und Besprechung in den Übungsgruppen • Notwendige Voraussetzung für Scheinvergabe: 50% der Maximalpunktzahl aller Übungsblätter zusammen • zwischen 50% und 65% der Punkten u.U. mündliche Prüfung zwecks Entscheidung über Scheinvergabe • PuPS-Übungsschein zur Teilnahme an der Einzelklausur „PuPS“ oder der Kombi-Klausur „DigInf & PuPS“ erforderlich (außer für Bachelor-Studenten) • DigInf-Übungsschein in diesem Semester für keine unserer Klausuren erforderlich PuPS 42 21