Informatik-II Vorlesungs- und U bungsskript Stefan Edelkamp Institut fur Informatik Georges Kohler Allee 79110 Freiburg eMail: [email protected] 12. Juli 2000 2 Das Vorlesungsskript ist in Anlehnung an die Informatik II - Vorlesung von Prof. Dr. Ottmann an der Universitat Freiburg im Sommersemester 2000 entstanden. Es bietet eine sinnvolle Erganzung zum Buch Algorithmen und Datenstrukturen (Ottmann/Widmayer, erschienen im Spektrum Verlag) insbesondere in der Bereitstellung von Java-Programmen, Aufgaben und deren Musterlosungen. Ich wunsche allen algorithmisch interessierten Lesern viel Freude an dem Skriptum und bin uber Kommentare und Verbesserungsvorschlage stets dankbar. Freiburg, 12. Juli 2000 Stefan Edelkamp Inhaltsverzeichnis I Vorlesungsfolien 11 1 Leistungsanalyse 13 1.1 Leistungsverhalten von Algorithmen . . . . . . . 1.1.1 Fallunterscheidung . . . . . . . . . . . . 1.1.2 Messung des Leistungsverhaltens . . . . 1.1.3 Kostenmae . . . . . . . . . . . . . . . . 1.1.4 Beispiel . . . . . . . . . . . . . . . . . . 1.2 Funktionenklassen . . . . . . . . . . . . . . . . . 1.2.1 Landau'sche O-Notation . . . . . . . . . 1.2.2 Bemerkungen . . . . . . . . . . . . . . . 1.2.3 Visualisierung . . . . . . . . . . . . . . . 1.2.4 Aufgaben . . . . . . . . . . . . . . . . . 1.2.5 Losungen . . . . . . . . . . . . . . . . . 1.2.6 Weitere Eigenschaften . . . . . . . . . . 1.2.7 O-Kalkul . . . . . . . . . . . . . . . . . . 1.2.8 Hierarchie von Groenordnungen . . . . 1.2.9 Skalierbarkeiten . . . . . . . . . . . . . . 1.2.10 Exponentielle Algorithmen . . . . . . . . 1.2.11 Maximaler Zeitaufwand cost(A) in Java 1.3 Beispiele . . . . . . . . . . . . . . . . . . . . . . 1.3.1 Das Maxsummenproblem . . . . . . . . . 1.3.2 Der normale Algorithmus . . . . . . . . 1.3.3 Divide-And-Conquer . . . . . . . . . . . 1.3.4 Der clevere Algorithmus . . . . . . . . . 1.3.5 Zusammestellung der Ergebnisse . . . . . 2 Elementare Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 14 14 15 15 16 16 16 17 17 18 18 18 19 19 20 21 22 22 22 23 23 24 25 2.1 U bersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 2.2 Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3 INHALTSVERZEICHNIS 4 2.3 Arrays . . . . . . . . . . . . . . . . . . . . . . . 2.4 Listen . . . . . . . . . . . . . . . . . . . . . . . 2.4.1 Alternative mit Zeigern . . . . . . . . . . 2.4.2 Implementationen . . . . . . . . . . . . . 2.4.3 Grundtypen und Knoten . . . . . . . . . 2.4.4 Arraydarstellung . . . . . . . . . . . . . 2.4.5 Dynamisch . . . . . . . . . . . . . . . . . 2.4.6 Verfeinerung . . . . . . . . . . . . . . . . 2.4.7 Visualisierungen . . . . . . . . . . . . . . 2.4.8 Doppelte Verkettung . . . . . . . . . . . 2.5 Stapel/Stack . . . . . . . . . . . . . . . . . . . . 2.5.1 Beispiel: Tower-of-Hanoi Problem . . . . 2.5.2 Rekursive Losung . . . . . . . . . . . . . 2.5.3 Aufrufbaum . . . . . . . . . . . . . . . . 2.5.4 Spezielle Stackimplementierung fur TOH 2.5.5 Testen: Tower-of-Hanoi-Stack . . . . . . 2.6 Schlange/Queue: . . . . . . . . . . . . . . . . . 2.6.1 Operationen . . . . . . . . . . . . . . . . 2.6.2 Anwendung: Topologische Sortierung . . 2.6.3 Verwendete Datenstrukturen . . . . . . . 2.6.4 Beispiel: . . . . . . . . . . . . . . . . . . 2.7 Mengen . . . . . . . . . . . . . . . . . . . . . . 3 Elementare Sortierverfahren 3.1 Das Sortierproblem . . . . . . . . . . . 3.1.1 Allgemeine Sortierverfahren . . 3.1.2 Elementare Sortierverfahren . . 3.2 Grundlegende Implementation . . . . . 3.3 Auswahlsort . . . . . . . . . . . . . . . 3.4 Sortieren durch Einfugen . . . . . . . . 3.5 Shellsort . . . . . . . . . . . . . . . . . 3.5.1 Beispiel zu Shellsort . . . . . . 3.5.2 Implementation: Shellsort . . . 3.6 Mergesort . . . . . . . . . . . . . . . . 3.6.1 Verschmelzen zweier Teilfolgen . 3.6.2 Analyse von Mergesort . . . . . 3.6.3 Reines 2-Wege-Mergesort . . . . 3.7 Quicksort . . . . . . . . . . . . . . . . 3.7.1 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 29 29 29 30 31 32 32 33 34 36 36 37 38 38 40 41 41 42 43 43 45 47 48 48 49 50 52 53 54 54 54 56 56 57 58 59 59 INHALTSVERZEICHNIS 5 3.7.2 Implementation: Aufteilungsschritt . . . . . . . . . . . . . . . . . . 61 3.7.3 Analyse von Quicksort . . . . . . . . . . . . . . . . . . . . . . . . . 62 3.7.4 Average Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 4 Schnelle Sortierverfahren 4.1 4.2 4.3 4.4 Kriterien fur Sortierverfahren . . . . . . . . . State-of-the-Art . . . . . . . . . . . . . . . . . Average Case Analyse Quicksort . . . . . . . . Clever-Quicksort . . . . . . . . . . . . . . . . 4.4.1 Implementation . . . . . . . . . . . . . 4.5 Heapsort . . . . . . . . . . . . . . . . . . . . . 4.5.1 Veranschaulichung . . . . . . . . . . . 4.5.2 Entfernen des Maximums . . . . . . . 4.5.3 Versickern . . . . . . . . . . . . . . . . 4.5.4 Erstellen eines Heaps . . . . . . . . . . 4.5.5 Iteriertes Versickern . . . . . . . . . . . 4.5.6 Analyse . . . . . . . . . . . . . . . . . 4.5.7 Bottom-up-Heapsort . . . . . . . . . . 4.5.8 Bottom-Up-Heapsort: Implementation 4.5.9 Ergebnisse . . . . . . . . . . . . . . . . 4.6 WEAK-HEAPSORT . . . . . . . . . . . . . . 4.6.1 Verschmelzen . . . . . . . . . . . . . . 4.6.2 Generierung eines Weak-Heaps . . . . . 4.6.3 Sortierungsphase . . . . . . . . . . . . 4.6.4 Arrayeinbettung . . . . . . . . . . . . . 4.6.5 Implementation . . . . . . . . . . . . . 4.6.6 Laufzeitanalyse . . . . . . . . . . . . . 4.7 Quick-Heapsort . . . . . . . . . . . . . . . . . 4.7.1 EXTERNAL-HEAPSORT . . . . . . . 4.7.2 Implementation: QUICK-HEAPSORT 5 Spezielle Sortierverfahren 5.1 Radixsort . . . . . . . . . . . . . . . . . 5.1.1 Radix-exchange-sort . . . . . . . 5.1.2 Radix-exchange-sort . . . . . . . 5.1.3 Analyse von Radix-exchange-sort 5.1.4 Implementation: Decomposable . 5.1.5 Implementation: Ganzzahlen . . . 5.1.6 Implementation: Zeichenketten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 68 69 70 73 73 75 76 76 77 78 80 80 81 82 83 84 84 85 86 86 87 88 89 89 90 91 92 92 92 93 94 94 95 INHALTSVERZEICHNIS 6 5.2 5.3 5.4 5.5 5.1.7 Implementation: Radix-Exchange . . . . Sortieren durch Fachverteilung . . . . . . . . . . 5.2.1 Analyse . . . . . . . . . . . . . . . . . . 5.2.2 Implementation: Fachverteilung . . . . . Das Auswahlproblem . . . . . . . . . . . . . . . 5.3.1 Implementation (Rahmen) . . . . . . . . 5.3.2 Divide-&-Conquer Losung . . . . . . . . 5.3.3 Wahl des Pivotelements . . . . . . . . . 5.3.4 Median-of-Median . . . . . . . . . . . . . Sortieren vorsortierter Daten . . . . . . . . . . . 5.4.1 Optimale Nutzung der Vorsortierung . . Untere Schranke fur allgemeine Sortierverfahren 5.5.1 Entscheidungsbaume . . . . . . . . . . . 6 Suchverfahren und Suchbaume 6.1 Problemstellung . . . . . . . . . . . . 6.2 Binare Suche . . . . . . . . . . . . . 6.2.1 Binare Suche ohne Rekursion 6.2.2 Analyse . . . . . . . . . . . . 6.3 Fibonacci-Suche . . . . . . . . . . . . 6.3.1 Implementation . . . . . . . . 6.4 Exponentielle Suche . . . . . . . . . . 6.5 Interpolationssuche . . . . . . . . . . 6.6 Selbstanordnende lineare Listen . . . 6.6.1 Gute von Move-To-Front . . . 6.6.2 Amortisierung . . . . . . . . . 6.6.3 Exkurs Binarzahler . . . . . . 6.6.4 Move-To-Front Analyse . . . . 6.6.5 Schlufolgerung . . . . . . . . 6.7 Suchbaume . . . . . . . . . . . . . . 6.7.1 Suchbaume . . . . . . . . . . 6.7.2 Beispiel . . . . . . . . . . . . 6.7.3 Einfugen/Suchen . . . . . . . 6.7.4 Sonderfalle . . . . . . . . . . . 6.7.5 Loschen . . . . . . . . . . . . 6.7.6 Durchlaufreihenfolgen . . . . 6.7.7 Implementation . . . . . . . . 6.7.8 Interne Pfadlange . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 97 97 98 100 100 101 102 102 103 104 105 105 107 108 109 109 110 112 112 114 115 116 116 116 117 118 119 120 120 121 122 123 124 125 125 126 INHALTSVERZEICHNIS 7 7 Balancierte Baume 129 7.1 Durchlaufreihenfolgen . . . . . . . . . . . . . . 7.1.1 Eigenschaften . . . . . . . . . . . . . . 7.1.2 Sortieren mit naturlichen Suchbaumen 7.1.3 Interne Pfadlange . . . . . . . . . . . . 7.1.4 Implementation . . . . . . . . . . . . . 7.2 AVL-Baume . . . . . . . . . . . . . . . . . . . 7.2.1 Einfugen . . . . . . . . . . . . . . . . . 7.2.2 Entfernen . . . . . . . . . . . . . . . . 7.2.3 Implementation . . . . . . . . . . . . . 7.2.4 Rotationen . . . . . . . . . . . . . . . 7.2.5 Einfugen . . . . . . . . . . . . . . . . . 7.2.6 Loschen . . . . . . . . . . . . . . . . . 7.3 Bayer-Baume . . . . . . . . . . . . . . . . . . 7.3.1 Tiefe . . . . . . . . . . . . . . . . . . . 7.3.2 Einfugen . . . . . . . . . . . . . . . . . 7.3.3 Loschen . . . . . . . . . . . . . . . . . 7.3.4 Anhangen (2-3 Baum) . . . . . . . . . 7.3.5 Aufteilen (2-3 Baum) . . . . . . . . . . 7.4 Bruder-Baume . . . . . . . . . . . . . . . . . . 7.4.1 Hohe . . . . . . . . . . . . . . . . . . . 7.4.2 Eigenschaften . . . . . . . . . . . . . . 7.4.3 Implementation Bruder-Baume . . . . 7.4.4 Suche und Einfugen in Bruder-Baumen 7.4.5 Falle in Up . . . . . . . . . . . . . . . 7.4.6 Weitere Falle in Up . . . . . . . . . . . 7.5 Rot-Schwarz-Baume . . . . . . . . . . . . . . 8 Hashverfahren 8.1 Geschlossene Hashverfahren . . . 8.1.1 Hashverfahren . . . . . . . 8.1.2 Hashverfahren|Beispiele . 8.1.3 Probleme . . . . . . . . . 8.1.4 Hashfunktion . . . . . . . 8.1.5 Hashverfahren . . . . . . . 8.1.6 Implementation in Java . 8.1.7 Wahl der Hash-Funktion . 8.1.8 Multiplikative Methode . . 8.1.9 Universelles Hashing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130 130 130 131 131 133 133 133 134 135 136 137 138 138 138 139 139 140 141 141 143 144 144 145 146 148 149 150 150 150 151 152 152 153 153 154 155 INHALTSVERZEICHNIS 8 8.1.10 Beispiel . . . . . . . . . . . . . . . . . 8.1.11 Universelles Hashing . . . . . . . . . . 8.1.12 Hashing mit Verkettung der U berlaufer 8.1.13 Verkettung der U berlaufer . . . . . . . 8.1.14 Implementation in Java . . . . . . . . 8.1.15 Implementation: Einfugen . . . . . . . 8.1.16 Implementation: Loschen . . . . . . . . 8.1.17 Test-Programm . . . . . . . . . . . . . 8.1.18 Analyse der direkten Verkettung . . . . 8.2 Oene Hashverfahren . . . . . . . . . . . . . . 8.2.1 Situation im Oenen Hashing . . . . . 8.2.2 Sondierungsfolgen . . . . . . . . . . . . 8.2.3 Kriterien . . . . . . . . . . . . . . . . . 8.2.4 Implementation: Oene Hashtabelle . . 8.2.5 Suchen . . . . . . . . . . . . . . . . . . 8.2.6 Einfugen . . . . . . . . . . . . . . . . . 8.2.7 Test-Programm . . . . . . . . . . . . . 8.2.8 Lineares Sondieren . . . . . . . . . . . 8.2.9 Quadratisches Sondieren . . . . . . . . 8.2.10 Uniformes Sondieren . . . . . . . . . . 8.2.11 Analyse ideales Hashing . . . . . . . . 8.2.12 Double Hashing . . . . . . . . . . . . . 8.2.13 Beispiel . . . . . . . . . . . . . . . . . 8.2.14 Verbesserung der erfolgreichen Suche . 8.2.15 Verbesserung der erfolgreichen Suche . 8.2.16 Brent und Binarbaum Sondieren . . . . 8.2.17 Beispiel . . . . . . . . . . . . . . . . . 8.2.18 Verbesserung der erfolglosen Suche . . 8.2.19 Beispiel: Ordered double Hashing . . . 8.2.20 Implementation: Suchen . . . . . . . . 8.2.21 Implementation Einfugen . . . . . . . . 9 Graphalgorithmen 9.1 Graphen . . . . . . . . . . . . . . . . . . 9.1.1 Implementation . . . . . . . . . . 9.1.2 Implementation Adjazenzmatrix . 9.1.3 Implementation Adjazenzliste . . 9.1.4 Tiefensuche . . . . . . . . . . . . 9.1.5 Topologische Sortierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 156 156 157 158 158 159 159 160 162 162 162 163 163 164 165 166 167 167 168 168 170 170 171 172 172 173 174 174 175 175 177 178 178 179 180 181 181 INHALTSVERZEICHNIS 9 II Aufgaben 183 10 Theorie 11 Praxis 185 229 10 INHALTSVERZEICHNIS Teil I Vorlesungsfolien 11 Kapitel 1 Leistungsanalyse 13 KAPITEL 1. LEISTUNGSANALYSE 14 1.1 Leistungsverhalten von Algorithmen Ezienzanalyse: Speicherplatzkomplexitat: Wird primarer, sekundarer Speicherplatz ezient genutzt? Laufzeitkomplexitat: Steht die Laufzeit im akzeptablen/vernunftigen/optimalen Verhaltnis zur Aufgabe? Theorie: liefert untere Schranke, die fur jeden Algorithmus gilt, der das Problem lost. Spezieller Algorithmus liefert obere Schranke fur die Losung des Problems. Erforschung von oberen und unteren Schranken: Eziente Algorithmen und Komplexitatstheorie (Zweige der Theoretischen Informatik) 1.1.1 Fallunterscheidung Sei x Eingabe, jxj Lange von x, P das betrachtete Problem und TP (x) die Laufzeit von P auf x. Der beste Fall (best case): Oft leicht zu bestimmen, kommt in der Praxis jedoch selten vor. Der schlechteste Fall (worst case): Gibt Sicherheit, meist leicht zu bestimmen. Fur viele Algorithmen Laufzeit auf Eingaben bestimmter Lange gleich gro. TP (n) = supfTP (x)j jxj = n; x Eingabe fur P g Im amortisierten worst case wird der durchschnittliche Aufwand fur eine schlechtestmogliche Folge von Operationen gemessen (technisch anspruchsvoll). Der mittlere Fall (average case): Nicht leicht zu handhaben, in der Praxis jedoch relevant. Denition (qn Wahrscheinlichkeitsverteilung): TP;q (n) = Pjxj n;x Eingabe fur P TP (x)qn(x) Woruber wird gemittelt? Trit (uniforme) Wahrscheinlichkeitsverteilung zu? n = 1.1.2 Messung des Leistungsverhaltens 1. Betrachte konkrete Implementierung auf konkreter Hardware. Mi Laufzeit fur reprasentative Eingaben. 2. Berechne Verbrauch an Platz und Zeit fur idealisierte Referenzmachine: a) Random Access Machine (RAM) b) Registermachine (RM) c) Turingmachine (TM) 1.1. LEISTUNGSVERHALTEN VON ALGORITHMEN 15 3. Bestimme Anzahlen gewisser (teurer) Grundoperationen, z.B. # Vergleiche bzw. # Bewegung von Daten (beim Sortieren) # Multiplikationen / Divisionen (fur numerische Verfahren) Fur 2. und 3.: Beschreibe Aufwand eines Algorithmus als Funktion der Groe des Inputs (kann verschieden gemessen werden). 1.1.3 Kostenmae Einheitskostenma: Annahme, jedes Datenelement belegt nur beschrankten Speicher- platz. Damit: Groe des Input bestimmt durch Anzahl der Datenelemente Beispiel: Sortierproblem Logarithmisches Kostenma: Annahme, ein einzelnes Datenelement kann beliebig gro werden, die Groe beeinut die Komplexitat. Groe der Eingabe bestimmt durch die Summe der Groen der Elemente. (fur n > 0 ist die # Bits zur Darstellung von n = dlog (n + 1)e) Beispiel: Primzahlzerlegung (je groer die Zahl desto schwieriger) 2 1.1.4 Beispiel Taktzahl eines Von-Neuman Addierwerks bei der Addition von 1 zu einer Zahl i der Lange n, d.h. 0 i 2n . Sie entspricht 1 plus der Anzahl der Einsen am Ende der 1 Binardarstellung von i. Die worst case Rechenzeit betragt n + 1 Takte. Wir nehmen eine Gleichverteilung auf der Eingabenmenge an. Es gibt 2n k Eingaben, die mit (0; 1; : : : ; 1) enden, wobei am Ende k 1 Einsen stehen. Hinzu kommt die Eingabe i = 2n 1 fur die das Addierwerk n + 1 Takte benotigt. Die average case Rechenzeit Ta (n) betragt also: P n 2 ( Pkn 2n k k + (n + 1)) Es ist kn 2n k k = (Tafel) = 2n 2 n. n n Demnach ist Ta(n) = 2 (2 2 n + (n + 1)) = 2 2 n. Es genugen also im Durchschnitt 2 Takte um ein Inkrement von 1 durchzufuhren. 1 1 +1 +1 16 1.2 Funktionenklassen KAPITEL 1. LEISTUNGSANALYSE Gro-O-Notation: O-, - und - Notation beschreiben obere, untere bzw. genaue Schranken. Idee: Beim Aufwand kommt es auf Summanden und konstante Faktoren letztendlich nicht an. Grunde: - Interesse an asymptotischen Verhalten fur groe Eingaben, - Genaue Analyse oft technisch sehr aufwendig/unmoglich - Lineare Beschleunigungen sind leicht moglich (Tausch von Hard- und Software) Ziel: Einteilung der Komplexitatsmessungen nach Funktionenklassen, z.B. O(f ) Menge von Funktionen, die in der Groenordnung von f sind. 1.2.1 Landau'sche O-Notation f = O(g) (in Worten: f wachst nicht schneller als g), wenn 9c 2 ; n 2 ; 8n n : f (n) cg(n). f = (g) (f wachst mindestens so schnell wie g), wenn g = O(f ) ist. f = (g) (f und g sind von der gleichen Wachstumsordnung), wenn f = O(g) und g = O(f ) gelten. f = o(g) (f wachst langsamer als g), wenn f (n)=g(n) eine Nullfolge ist. f = !(g) (f wachst schneller als g), wenn g = o(f ) ist. + IR 0 IN 0 In Ottmann/Widmayer O(g) = ff j 9a > 0; b > 0; 8nf (n) ag(n) + bg und (g) = fgj 9c > 0; 9 1 viele n : f (n) cg(n)g 1.2.2 Bemerkungen Schreibweise: f = O(g) statt f 2 O(g) Gleichungsfolgen: In der O-Notation mussen stets von links nach rechts gelesen werden. So ist 10n + n = O(n ) = o(n ) sinnvoll, wahrend 10n + n = o(n ) = O(n ) ebenso sinnlos ist wie 10n + n = O(n ) = (n ) Minimalitat: Die angegebene Groenordnung mu nicht minimal gewahlt sein (s.o.) Asymptotik: Wie gro n sein mu bleibt unklar 2 2 2 2 2 2 3 3 2 1.2. FUNKTIONENKLASSEN 17 Konstanten: Die Konstanten c und n (bzw. a und b) haben fur kleine n groen Einu. 0 1.2.3 Visualisierung f (n) 2 O(g(n)) , 9n 2 ; c 2 8n n : f (n) cg(n) ( nlim !1 f (n)=g (n) = c 0 IN + IR 0 c=0.5 f(n)= n g(n)=n n0 Die Regel von L'Hospital nlim !1 f (n)=g (n) = c ( nlim !1 f 0 (n)=g 0(n) = c 1.2.4 Aufgaben Aufgabe 1: Zeigen Sie durch die Wahl geeigneter Konstanten c und n , da (n + 1) in 0 2 der Klasse O(n ) liegt. Aufgabe 2: Es gilt die folgende Beziehung: Falls der Grenzwert von f (n)=g(n) fur n gegen unendlich durch eine Konstante c beschrankt ist (wir schreiben limn!1 f (n)=g(n) c), so gilt f (n) 2 O(g(n)). Zeigen Sie unter Verwendung dieses Sachverhaltes abermals (n+1) 2 O(n ). 2 2 2 KAPITEL 1. LEISTUNGSANALYSE 18 Aufgabe 3: Die Regel von L'Hospital besagt: Falls der Grenzwert f 0(n)=g0(n) fur n gegen unendlich existiert (f 0 ist die Ableitung von f ), so ist er gleich dem Grenzwert von f (n)=g(n). Zeigen Sie mit Hilfe dieser Regel letztmalig (n + 1) 2 O(n ). Aufgabe 4: Sei P (n) ein Polynom vom Grad k. Zeigen Sie: O(log P (n)) = O(log n) 2 2 1.2.5 Losungen Aufgabe 1: Zu zeigen ist (n +1) p cn oder (pc 1)n 2n 1 0. Wir wahlen c = 2. Die Nullstellen von n 2np 1 sind 2 + 1 und 2 + 1. Die Parabel ist nach oben geonet. Also ist fur n n = d 2 + 1e der Wert (n + 1) 2n und demnach (n + 1) 2 O(n ). Aufgabe 2: limn!1 n n n = limn!1 n =nn =n = limn!1(1 + 2=n + 1=n ) = 1: Aufgabe 3: 2 2 2 2 2 0 2 (1+2 2 +2 +1 2 2 +1 2 2 2) 2 2 Da limn!1 2=2 = 1 gilt, ist limn!1 nn = 1 und somit limn!1 n2 n2n = 1: Aufgabe 4: P (n) = O(nk ) und damit O(log P (n)) = O(log nk ) = O(k log n) = O(log n). 2 +2 2 +2 +1 1.2.6 Weitere Eigenschaften 1. Fur monoton steigende Funktionen f 6 0 sind beide Denitionen fur Gro-O aquivalent. 2. Es gibt Funktionen in denen beide Denitionen voneinander abweichen (erste Denition ist scharfer). 3. Fur alle k ist nk 2 o(2n) 4. Es seien p und p Polynome vom Grad d bzw. d , wobei die Koezienten von nd1 und nd2 positiv sind. Dann gilt: 1 2 1 2 a) p 2 (p ) , d = d 1 2 1 b) p 2 o(p ) , d < d 1 2 1 2 2 b) p 2 !(p ) , d > d 1 2 1 2 5. Fur alle k > 0 und > 0 gilt logk n = o(n) 6. 2n= = o(2n) Beweis: U bungsaufgabe. Gleichzeitig ein Beleg, da die Analysis-Vorlesung Anwendung hat. 2 1.2.7 O-Kalkul f = O(f ) O(O(f )) = O(f ) 1.2. FUNKTIONENKLASSEN 19 kO(f ) = O(f ) fur konstantes k O(f + k) = O(f ) fur konstantes k Additionsregel: O(f ) + O(g) = O(maxff; gg) Multiplikationsregel: O(f ) O(g) = O(f g). Beweis: Wissen 9c ; c 2 und n ; n 2 mit T (n) c f (n) fur alle n n und T (n) c g(n) fur alle n n . 1 2 + IR 2 2 1 2 IN 1 1 1 2 Additionsregel: T (n)+ T (n) c maxff (n); g(n)g fur c = c + c und n maxfn ; n g. Multiplikationsregel: T (n)T (n) cf (n)g(n) fur c = c c und n maxfn ; n g. Additionsregel fur die Komplexitat der Hintereinanderausfuhrung der Programme. Multiplikationsregel fur die Komplexitat von ineinandergeschachtelten Schleifen. 1 2 1 1 2 1 2 2 0 0 1 1 2 2 1.2.8 Hierarchie von Groenordnungen O(1): konstante Funktionen O(log n): Logarithmische Funktionen O(log n): Quadratisch logarithmische Funktionen O(n): Lineare Funktionen O(n log n): - keine spezielle Bezeichnung O(n ): quadratische Funktionen O(n ): kubische Funktionen O(nk ): polynomielle Funktionen genauer: f heit polynomiell beschrankt, wenn es ein Polynom p mit f = O(p) gibt. O(2n): exponentielle Funktionen genauer: f wachst exponentiell, wenn es ein > 0 mit f = (2n ) gibt. 2 2 3 1.2.9 Skalierbarkeiten Annahme: Rechenschritt 0.001 Sekunden. Maximale Eingabelange bei gegebener Re- chenzeit. Laufzeit T(n) 1 Sekunde 1 Minute 1 Stunde n 1000 60000 3600000 n log n 140 4895 204094 n 31 244 1897 n 10 39 153 2n 9 15 21 Maximale Eingabelange in Abhangigkeit der Technologie (log 10p = log p +log 10 log p, 3:16 10; 2:15 10; 2 : 10) 2 3 2 3 33 KAPITEL 1. LEISTUNGSANALYSE 20 Laufzeit T(n) alt neu (d.h. 10-mal schneller) n p 10p n log n p (fast 10)p n p 3:16p n p 2:15p n 2 p p + 3:3 100n < dn =125e ab n > 111 10n < :5n log n ab n > 2 2 3 3 20 1.2.10 Exponentielle Algorithmen Seerosenbeispiel: Schachbrett (Reis), Zinseszins (Manhattan), Borse (Kybernetik), Zellteilung (Qualitativ-Quantitativ), Radioaktiver Zerfall (Exponentiell langsam) In der Vorlesung Informatik III wird die Grauzone zwischen den garantiert polynomiellen und garantiert exponentiellen Laufzeiten analysiert. EXPTIME PSPACE NP P NC 1.2. FUNKTIONENKLASSEN 1.2.11 Maximaler Zeitaufwand cost(A) in Java 1. A ist einfache Zuweisung (read, write, . . . ): cost(A) = const 2 O(1) 2. A ist Folge von Anweisung: Additionsregel anwenden 3. A ist if-Anweisung: a) if (cond) B; cost(A) = cost(cond) + cost(B ) b) if (cond) B; else C cost(A) = cost(cond) + maxfcost(B ); cost(C )g 4. A ist eineP Schleife: cost(A) = Umlauf ifcost(Anweisungen i) + cost(Terminierungsbedingung i)g oft cost(A) = #Umlaufe fcost(Anweisungen) + cost(Terminierungsbedingung)g 21 KAPITEL 1. LEISTUNGSANALYSE 22 1.3 Beispiele Problem Bubble-Sort: Sortiere die ganzen Zahlen in dem Array a[1..n] Algorithmus: repeat for i= 1..n-1 if (a[i] > a[i+1]) swap(a[i],a[i+1]) until (keine Vertauschungen mehr notwendig) Analyse der Anzahl von Vergleichen: worst case: Innere Schleife: n 1 Vergleiche, maximal n Durchlaufe (Minimum an a[n]) der ausseren Schleife ) O(n2) best case: Sortiertes Inputarray: Ende nach einem Durchlauf ) O(n) average case: Fakt: Minimum wandert pro Durchlauf der ausseren Schleife 1 Schritt nach links. Annahme: Jede Position fur Min gleichwahrscheinlich. ) mindestens 1O (n)+2O (n)+:::+nO (n) = O(n2) viele Vergleiche werden ausgefuhrt. n 1.3.1 Das Maxsummenproblem Problem: Finde ein Index-Paar (i; j ) in einem Array a[1..n] fur das f (i; j ) = ai +: : :+aj maximal ist. Als Rechenschritte zahlen arithmetische Operationen und Vergleiche. Der naive Algorithmus: Es sollen alle f (i; j ) berechnet werden und dann der maximale f -Wert ermittelt werden. Oensichtlich genugen zur Berechung von f (i; j ) genau j i Additionen. Der Algoritmus startet mit max f (1; 1) und aktualisiert max wenn notig. Es gibt j Paare der Form (; j ) P # Vergleiche: V (n) = ( P jn jP) 1 = n(n + 1)=2 1 # Additionen: A (n) = in jn(j i) = (Tafel) = 1=6n 1=6n # Zusammen: T (n) = V (n) + A (n) = 1=6n + 1=2n + 1=3n 1 1 1 1 1 1 3 1 1 3 1 2 1.3.2 Der normale Algorithmus Der naive Ansatz berechnet a + a fur f (1; 2); f (1; 3); : : :; f (1; n), also (n 1)-mal. Besser geht's mit folgender Erkenntnis. Es gilt: f (i; j + 1) = f (i; j ) + aj Damit braucht man fur alle f (i; )-Werte genau (n i) Additionen. 1 2 +1 1.3. BEISPIELE 23 # Vergleiche: V (n) = VP(n) = n(n + 1)=2P 1 # Additionen: A (n) = in(n i) = kn k = n(n 1)=2 # Zusammen: T (n) = V (n) + A (n) = n 1. 2 1 2 1 2 2 2 2 1 1 1.3.3 Divide-And-Conquer Annahme: n = 2k . Unterteilung in drei Klassen: 1 i; j n=2 (Divide-And-Conquer) 1 i n=2 < j n (Direkt) n=2 < i j n (Divide-And-Conquer) g(i) = ai + : : : + an= und h(j ) = an= + : : : + aj Dann: f (i; j ) = g(i) + h(j ) Um f zu maximieren, maximiere g und h einzeln. Berechne nacheinander g(n=2) = a[n=2]; g(n=2 1); : : : ; g(1) und den max. g Wert in (n=2 1) Vergleichen und (n=2 1) Additionen. Berechne h analog. Damit insgesamt n 1 Additionen und n 2 Vergleiche, also 2n 3 Operationen. Rekursionsgleichung (2 Vergleiche um Maximum der Gruppen zu nden) T (1) = 0 T (2k ) = 2T (2k ) + 2 2k 1 ) (Tafel) ) T (2k ) = (2k 1)2k + 1 = (2 log n 1)n + 1 2 3 3 3 2+1 1 3 1.3.4 Der clevere Algorithmus Wir wollen Problem in [1::n] losen und verwalten dabei nach dem Lesen von ak in max den groten Wert von f (i; j ) aller Paare (i; j ) fur 1 i j k. Fur k = 1 setzen wir max auf a . Konkurrenten sind Paare (i; j ) mit k < i und Paare (i; j ) mit i k < j . uber die erste Menge wissen wir noch nichts, fur die zweite Menge verwalten wir max = max ik fg(i)g mit g(i) = ai + : : : + ak . Sei nun ak gelesen. gneu(i) = galt(i) + ak , fur 1 i k gneu(i) = ak , fur i = k + 1 Also maxneu = maxfmaxalt +ak ; ak g Fur maxneu kommen folgende Paare in Frage: 1 i j k (maximaler Wert maxalt ) 1 i k; j = k + 1 (maximaler Wert maxneu) Bei der Verarbeitung von ak , 2 k n, genugen also 3 Operationen, demnach ist T (n) = 3n 3: 1 1 +1 +1 +1 +1 4 +1 KAPITEL 1. LEISTUNGSANALYSE 24 1.3.5 Zusammestellung der Ergebnisse T (n) = 1=6n + 1=2n + 1=3n 1 T (n) = n 1 T (n) = (2 log n 1)n + 1 T (n) = 3n 3 n T (n) T (n) T (n) T (n) 2 19 15 13 9 2 815 255 113 45 2 45759 4095 705 189 2 2829055 65535 3841 765 2 179481599 1048575 19457 3069 2 > 5 10 10 950273 98301 Die Rechenzeiten richten sich nach den Groenordnungen (n ; n ; n log n; n) und nicht nach den Vorfaktoren. Es sollte sich das beruhigende Gefuhl breitmachen, da es sich lohnt clever zu sein. Ottmann/Widmayer: Berechnungen in O-Notationen 3 1 2 2 2 3 4 1 2 3 4 2 4 6 8 10 15 12 9 3 2 Kapitel 2 Elementare Datenstrukturen 25 26 2.1 U bersicht KAPITEL 2. ELEMENTARE DATENSTRUKTUREN Motivation: Wozu sind Datenstrukturen nutzlich? Arrays mit Adressierung Listen mit - 4 verschiedenen Implementierungen Stapel/Schlangen - Anwendung Stapel: Tower of Hanoi - Anwendung Schlange: Topologische Sortierung Mengen dargestellt als - Bitvektoren 2.2. MOTIVATION 27 2.2 Motivation Algorithmus: Endliche Sequenz von Instruktionen, die angeben, wie ein Problem gelost werden soll. Genauer: Berechenbare Prozedur zusammen mit einer Menge von moglichen Eingaben, die einen oder eine Menge von Werten als zulassige Ausgabe liefert. Datenstrukturen (DS): Dienen zur Organisation von Daten, um bestimmte Operationen (ezient) zu unterstutzen. Hier und heute: Werden DS isoliert betrachtet, in der Praxis sind sie wichtige Bestandteile von Algorithmen. Verbindung: Datenstrukturen lassen sich selbstverstandlich ineinander schachteln. Eignung: Demnach ist es stets nutzlich, sich Anwendungs- und Nichtanwendungssituationen zu uberlegen Unabhangigkeit: Darstellung im Grunde unabhangig von der Programmiersprache Konkretisierung: Imperative Programmiersprachen, speziell: Programmiersprache Java KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 28 2.3 Arrays 1-dimensional: Array a fur den Bereich [1::n] sieht n Positionen fur Daten des gewahlten Typs vor. Darstellung im Rechner: Array liegt fur ein n an Position N + 1; : : : N + n. Mit a[i] sprechen wir das Datum an Position N + i an. Wesentlich: Arrays erlauben den Zugri auf Komponenten uber einen (berechenbaren) Index Zugrie: O(1). Keine Unterstutzung von Operationen vom Typ Nimm a[i] und fuge es zwischen a[j ] und a[j + 1] ein. 2-dimensional: Array [1::n ] [1::n ]. Position (i; j ) bei Anfangsadresse N ndet sich an N + (i 1)n + j k-dimensional: Array [1::n ] : : : [1::nk ] Element a[i ] : : : [ik ] ndet sich an Position 1 2 2 1 1 8 9 < X Y = N +: (ij 1) nl ; + ik j k j<lk 1 1 2.4. LISTEN 29 2.4 Listen Denition: Sei L = (a ; a ; : : : ; an) geordnete Folge von Objekten eines bestimmten 1 2 Typs, dann a Anfangsobjekt (Kopf), an Endobjekt (Schwanz), ai Nachfolger von ai, ai Vorganger von ai . Nachfolger von an und Vorganger von a ist null. Falls n = 0 heit die Liste leer. 1 +1 +1 1 Operationen: (x; L): Fuge das Objekt x in L ein. Ergebnis ist eine Liste. find(x; L): Falls x 2 L ist Antwort true, sonst false delete(x; L): Entferne x aus L. Falls x 62 L Fehlerbehandlung. Ergebnis ist eine Liste. Weiterhin: pred(x; L), succ(x; L), null(L), first(L), last(L), length(L), union(L ; L ; L), copy(L; L0 ), split(x; L; L ; L ) insert 1 2 1 2 2.4.1 Alternative mit Zeigern Zeiger (Handle): p zum Zugri, z.B. vom Typ Object Operationen: (x; p; L): Fuge das Objekt x an Stelle p in L ein. Ergebnis ist eine Liste. delete(p; L): Entferne Element an Stelle p aus L. Ergebnis ist eine Liste. search(x; L): Liefert als Zeiger die erste Position, an der x in L vorkommt, und null, falls x in L nicht vorkommt Weiterhin: pred(p; L), succ(p; L), null(L), first(L), last(L), length(L), union(L ; L ; L), copy(L; L0 ), split(p; L; L ; L ) insert 1 2 1 2 2.4.2 Implementationen Version 1: Als Array. Zeiger p vom Typ int (in search: p = 0 falls nicht vorhanden). Version 2: Einfach verkettet, Listenende null L - a 1 - a 2 - ::: - an r Version 3: Einfach verkettet, Listenanfang und -ende dummy Elementzeiger ist uber next zu erreichen KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 30 - a - 1 a - 2 ::: - an ? - 6 6 tail head Version 4: Doppelt verkettet, Listenanfang und Listenende null r - a 1 a 2 ::: 6 an r 6 head tail 2.4.3 Grundtypen und Knoten Der Grundtyp beinhaltet Schlussel bzw. Datensatz public class Grundtyp { int key; // eventuell weitere Komponenten Grundtyp() { this.key = 0; } Grundtyp(int key) { this.key = key; } public boolean equals(Grundtyp dat) { return this.key == dat.key; } public String toString() { StringBuffer st = new StringBuffer(""+key); return st.toString(); } } Knoten dient zur Speicherung des Dateninhaltes und der (einfachen) Verkettung von Listenelementen. Wird ausschliesslich in der zugehoerigen Listenklasse genutzt. class Knoten { Grundtyp dat; Knoten next; // Grundtyp // Zeiger auf Nachfolgerknoten Knoten(Grundtyp dat, Knoten next) { 2.4. LISTEN this.dat = dat; this.next = next; } } 2.4.4 Arraydarstellung Liste reprasentiert als Array: public class Liste { Grundtyp[] L; int elzahl; Liste(int maxelzahl) { L = new Grundtyp[maxelzahl+1]; elzahl = 0; } public int search(Grundtyp x) { L[0] = x; int pos = elzahl; while (!L[pos].equals(x)) pos--; return pos; } public void insert(Grundtyp x, int p) { for (int pos=elzahl; pos >= p; pos--) L[pos+1] = L[pos]; L[p] = x; elzahl++; } public void delete(int p) { elzahl--; for(int pos=p;pos<=elzahl;pos++) L[pos] = L[pos+1]; } public String toString () { StringBuffer st = new StringBuffer(""); for (int pos = 1; pos <= elzahl; pos++) st.append(pos == elzahl ? L[pos].toString(): L[pos].toString()+", "); return st.toString(); } } 31 32 KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 2.4.5 Dynamisch Liste implementiert als einfach verkettete Struktur: public class Liste { Knoten head; // Kopfelement Liste() { head = null; } // leere Liste public void insert (Grundtyp x) { head = new Knoten(x,head); } public boolean find(Grundtyp x) { for (Knoten i = head;i != null;i = i.next) if (i.dat.equals(x)) return true; return false; } public void delete(Grundtyp x) throws Exception { if (head.dat.equals(x)) head = head.next; for (Knoten i = head;i.next != null;i = i.next) if (i.next.dat.equals(x)) { i.next = i.next.next; return } throw new Exception("x kommt nicht vor"); } public String toString() { StringBuffer st = new StringBuffer(""); for (Knoten i = head;i != null;i = i.next) st.append(i.next == null ? i.dat.toString() : i.dat.toString()+", "); return st.toString(); } } 2.4.6 Verfeinerung . . . nach (Ottmann / Widmayer): public class Liste { 2.4. LISTEN 33 Knoten head, tail; Liste() { Grundtyp dummy = new Grundtyp(); head = new Knoten(dummy,null); tail = new Knoten(dummy,head); head.next = tail; } public void insert (Grundtyp x, Knoten p) { Knoten hilf; if (p == tail) hilf = tail; else hilf = p.next; p.next = new Knoten(p.dat,hilf); p.dat = x; if (p == tail) tail = tail.next; if (hilf == tail) tail.next = p.next; } public void delete (Grundtyp x) throws Exception { tail.dat = x; Knoten pos = head; while(!pos.next.dat.equals(x)) pos = pos.next; if (pos.next != tail) pos.next = pos.next.next; else throw new Exception("x kommt nicht vor"); if (pos.next == tail) tail.next = pos; } public Knoten search(Grundtyp x) { tail.dat = x; Knoten pos = head; do pos = pos.next; while (!pos.dat.equals(x)); if (pos == tail) return null; return pos; } ... } 2.4.7 Visualisierungen Einfache Verkettung Vor dem Einfugen 6 head a 1 - ::: - ap 1 - ap - ::: - an ? - 6 tail KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 34 Nach dem Einfugen - a - 1 ::: - ap 1 - x - ap - ::: 6 - an ? - 6 head tail Doppelte Verkettung Loschen in doppelt verketteter Liste: ::: a ap p ::: 1 6 p 2.4.8 Doppelte Verkettung public class Knoten { Grundtyp dat; // Grundtyp Knoten next; // Zeiger auf Nachfolgerknoten Knoten prev; // Zeiger auf Vorgaengerknoten ... } public class Liste { Knoten head, tail; Liste() { head = null; tail = null; } public void insert (Grundtyp x) { if (head == null) head = tail = new Knoten(x,null,null); else insert(x,head); } public void insert (Grundtyp x, Knoten p) { Knoten hilf = new Knoten(x,p.next,p); p.next = hilf; if (hilf.next == null) tail = hilf; else hilf.next.prev = hilf; } public void delete (Grundtyp x, Knoten p) { if (p == head) head = p.next; - ap +1 ::: ::: 2.4. LISTEN p.prev.next = p.next; if (p == tail) tail = p.prev; p.next.prev = p.prev; } public Knoten search(Grundtyp x) { for (Knoten i = head;i != null;i = i.next) if (i.dat.equals(x)) return i; return null; } } 35 KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 36 2.5 Stapel/Stack LIFO Speicher Operationen empty, push, pop (, peek) maxelzahl top rr rr 2 1 0 9 > = > ; 9 > > > = > > > ; frei Stapel Implementation: In Java in der Klassenbibliothek vorhanden: java.util.Stack public class Stack extends Vector { // Default Constructor: public Stack() // Public Instance methods public boolean empty(); public Object peek() throws EmptyStackException; public Object pop() throws EmptyStackException; public Object push(Object item); public int search(Object o) } } 2.5.1 Beispiel: Tower-of-Hanoi Problem Problem: n Scheiben: 1; 2; 3; : : : ; n, 3 Pfeiler: A,B,C (1,2,3). Invarianz: Immer kleinere Scheibe auf der Groeren. 2.5. STAPEL/STACK 37 A B C TOH: Live-Vorfuhrung 2.5.2 Rekursive Losung class Pfeiler { String name; Pfeiler(String n) { name=n;} public String toString(){ return name; } } public class ToHP { static long count = 0; static Pfeiler A, B, C; public static void main (String args[]){ int n = Integer.parseInt (args[3]); A = new Pfeiler (args[0]); B = new Pfeiler (args[1]); C = new Pfeiler (args[2]); umschichten (n, A, B, C); System.out.println(count+" Scheibenbewegungen."); } public static void umschichten (int n, Pfeiler von, Pfeiler nach, Pfeiler mit) { if (n > 0) { umschichten(n-1,von, mit, nach); System.out.println(von+" --> "+nach); count++; umschichten(n-1,mit, nach, von); } } } KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 38 2.5.3 Aufrufbaum Tupel der Form: (n; von; nach; mit) : 4,1,2,3 3,3,2,1 3,1,3,2 2,2,3,1 2,1,2,3 1,1,3,2 1,3,2,1 1,2,1,3 1,1,3,2 Stackreihenfolge: | | | | | | | | +--+ | | | | | | | | +--+ | | | | | | | | +--+ | | | | | | | | +--+ Problem: Ausgabe in inneren Knoten 2.5.4 Spezielle Stackimplementierung fur TOH Genauer: Lineare Liste ohne explizites Knotenelement class TohStack { int [] arr; TohStack next; TohStack (){ next = null; } // Konstruktor 1 TohStack (int [] a, TohStack n){ // Konstruktor 2 arr = a; 2.5. STAPEL/STACK next = n; } public boolean empty () { return next == null; } public void push (int a, int b, int c, int d) { int [] A = { a, b, c, d }; if ( empty () ) next = this; else next = new TohStack(arr, next); arr = A; } public int [] pop (){ int [] a = arr; if ( next == this ) next = null; else { arr = next.arr; next = next.next; } return a; } } 39 40 KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 2.5.5 Testen: Tower-of-Hanoi-Stack Idee: Auosung der Rekursion durch Verwaltung der lokalen Variablen auf einem Stack. public class tohStackTest { public static void main (String args[]) { TohStack s = new TohStack (); s.push (Integer.parseInt(args[0]), 1, 2, 3); while (!s.empty ()) { int [] a = s.pop(); if (a[0] < 0) System.out.println ( "bewege Scheibe Nr. "+(-a[0])+ " von Pfeiler "+a[1]+" nach "+a[2]); else if (a[0] != 0) { s.push (a[0]-1, a[3], a[2], a[1]); s.push ( -a[0], a[1], a[2], 0 ); s.push (a[0]-1, a[1], a[3], a[2]); } } } } // // // // // // > javac tohStackTest.java > java tohStackTest 4 bewege Scheibe Nr. 1 von Pfeiler 1 nach 3 bewege Scheibe Nr. 2 von Pfeiler 1 nach 2 bewege Scheibe Nr. 1 von Pfeiler 3 nach 2 ... 2.6. SCHLANGE/QUEUE: 41 2.6 Schlange/Queue: FIFO Speicher: empty, enqueue, dequeuefrei 0 6 @ size-1 6 tail @ @ head Schlange class Queue { Object element[]; int lastOp, tail, head, size; static final int deque = 1; static final int enque = 0; Queue(int sz) { size = sz; head = 0; tail = size-1; lastOp = deque; element = new Object[sz]; } public boolean empty() { return head==(tail+ 1)%size;} public void enqueue(Object elem) throws Exception ... public Object dequeue() throws Exception ... } 2.6.1 Operationen public void enqueue(Object elem) throws Exception { if ((head == (tail+1) % size) && lastOp == enque) throw new Exception("Queue full"); lastOp = enque; tail = (tail + 1) % size; element[tail] = elem; } public Object dequeue() throws Exception { KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 42 if ((head == (tail+1) % size) && (lastOp == deque)) throw new Exception("Queue empty"); Object temp = element[head]; lastOp = deque; head = (head + 1) % size; return temp; } Test public static void main(String argv[]) { Queue Q = new Queue(10); try { Q.enqueue(new String("Haus")); Q.enqueue(new Integer(10)); Object o = Q.dequeue(); System.out.println(o); } catch (Exception e) { System.out.println(e); } } 2.6.2 Anwendung: Topologische Sortierung Denition: Die Relation ist eine vollstandige bzw. partielle Ordnung auf M , wenn (1)-(4) bzw. (2)-(4) gelten. (1) 8x; y 2 M : x y oder y x (2) 8x 2 M : x x (3) 8x; y 2 M : x y und y x ) x = y (4) 8x; y 2 M : x y und y z ) x z Beispiel: Potenzmenge 2M einer Menge M bezuglich Teilmengenrelation partiell aber nicht vollstandig geordnet. Topologische Sortierung: Gegeben M und partiell. Gesucht Einbettung in vollstandige Ordnung, d.h. die Element von M in eine Reihenfolge m ; : : : ; mn bringen, wobei mi mj fur i < j gilt. Lemma: Jede partiell geordnete Menge lat sich topologisch sortieren. Beweisidee: Es genugt, die Existenz eines minimalen Elements z in M zu zeigen. z mu nicht eindeutig sein. Wahle m = z und sortiere M fzg topologisch. 1 1 2.6. SCHLANGE/QUEUE: 43 2.6.3 Verwendete Datenstrukturen A: Array der Lange n, A[i] naturliche Zahl V : Array der Lange n, V [i] Zeiger auf L[i] L[i]: Liste von Zahlenpaaren Q: Queue (Schlange), die ebenfalls Zahlen enthalt Algorithmus: E ingabe: p Paare (i; j ), mit xi xj und M = fx1; : : : ; xn g (1) Setze alle A[i] auf 0, alle L[i] und Q seien leer (2) Wird (j; k) gelesen, wird A[k] um 1 erhoht und in L[j ] eingetragen. (3) Durchlaufe A: Schreibe alle k mit A[k] = 0 in Q (4) While Q 6= fg j = Q.dequeue () Gebe xj aus Durchlaufe L[j ]: Wird k gefunden, wird A[k] um 1 verringert. Falls A(k) = 0 ist, Q.enqueue (k) Satz: Der Algorithmus lost das Problem Topologische Sortierung in O(n + p) Zeit. 2.6.4 Beispiel: Eingabe: (1,3), (3,7), (7,4), (4,6), (9,2), (9,5), (2,8), (5,8), (8,6), (9,7), (9,4) Datenstruktur nach der Vorverarbeitung: 1 2 3 4 5 A:[0 1 1 2 1 V:1,3 2,8 3,7 4,6 5,8 6 2 7 8 9 2 2 0] 7,4 8,6 9,2 9,5 9,7 9,4 Ablauf des Algorithmus: Q | 1 2 3 4 5 6 7 8 9 -----------+--------------------------{1,9} | 0 1 1 2 1 2 2 2 0 | | | KAPITEL 2. ELEMENTARE DATENSTRUKTUREN 44 | | | 2.7. MENGEN 45 2.7 Mengen Unterscheidung: Bitvektordarstellung und Listendarstellung Eignung: Bitvektordarstellung fur feste Grundmenge G = f1; : : : ; ng. und (relativ) groen Teilmengen M von G. Bitvektor-Darstellung: M durch Array A mit A[i] = 1 , i 2 M , und A[i] = 0 sonst. Operationen: - i 2 M? - fuge i zu M hinzu? - entferne i aus M ? - M =M [M ? - M =M \M ? -M =M M ? - M = M M ? 1 2 1 2 1 1 2 2 Machinenwortdarstellung: w Bits in einem Machinenwort. Komplexitat entweder O(1) oder O(dn=we) 46 KAPITEL 2. ELEMENTARE DATENSTRUKTUREN Kapitel 3 Elementare Sortierverfahren 47 48 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN 3.1 Das Sortierproblem Gegeben: Folge s[1]; : : : ; s[n] von n Satzen; jeder Satz s[i] hat einen Schlussel k[i]. Gesucht: Permutation derart, da die Anordnung der Satze gema die Schlussel in aufsteigende Reihenfolge bringt: k k k n : (1) (2) ( ) Ausgabe: Satze in aufsteigender Reihenfolge - in einem Array (internes Sortieren ) - in einer Datei auf Festplatte oder Magnetband (externes Sortieren ) In situ-Verfahren: Verfahren, die wenig zusatzlichen Speicherplatz benotigen. 3.1.1 Allgemeine Sortierverfahren Schlusselvergleiche (<, =, >) sind die einzige verwendbare Information zur Losung des Sortierproblems. Beispiele: (a) Elementare Verfahren: Laufzeit (worst case) Bubblesort, Auswahlsort Sortieren durch Einfugen, Shellsort (b) Mergesort (c) Quicksort (d) Clever-Quicksort (e) Heapsort (f) Weak-Heapsort 2. Untere Schranken 3. Spezielle Sortierverfahren: Schlussel sind ganze Zahlen Verwendung von , =, shift, mod, etc. (a) Radix (exchange-) Sort (b) Sortieren durch Fachverteilung 4. Sortieren vorsortierter Daten: Verfahren fur besondere Eingaben 3.1. DAS SORTIERPROBLEM 49 3.1.2 Elementare Sortierverfahren Laufzeitmessung: a. # Schlusselvergleiche (comparisons) Cmin(n) (best case) Cmax (n) (worst case) Cav (n) (average case) b. # Datenbewegungen (movements) Mmin , Mmax , Mav Im average case: Mittelung uber alle n! Anordnungen von n verschiedenen Satzen Messung der Anzahl der Vergleiche 1. Fur die meisten Sortierverfahren: Mx 2 O(Cx), x = max, min, av 2. Datenbewegung: Pointerzuweisung, Schlusselvergleich: Vergleich der einzelnen Zeichen Beispiel: Bubblesort Cmin(n) = n 1; Mmin = 0 Cmax (n) = 21 n(n 1) = Mmax Cav (n) 2 O(n ); 2 Mav (n) 2 O(n ) 2 50 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN 3.2 Grundlegende Implementation Rahmenprogramm: SortAlgTest.java: Programm zum Testen von Sortieralgorithmen public class SortAlgTest { public static void main(String args[]){ int vec[] = {15, 2, 43, 17, 4, 8, 47}; if (args.length != 0) { vec = new int [args.length]; for (int j=0; j<args.length;j++) { vec[j] = Integer.valueOf(args[j]).intValue(); } } /* t[0] wird als Stopper verwendet */ OrderableInt t[] = OrderableInt.array (vec); SortAlgorithm.printArray(t); SortAlgorithm.sort(t); SortAlgorithm.printArray (t); } } Interface: Orderable.java: Beschreibt das Verhalten vergleichbarer Objekte interface Orderable { public boolean equal public boolean greater public boolean greaterEqual public boolean less public boolean lessEqual public Orderable minKey } (Orderable (Orderable (Orderable (Orderable (Orderable (); o); o); o); o); o); Sortierbasisklasse: SortAlgorithm.java bietet abzuleitende Klasse fur die Sortieralgorithmen class SortAlgorithm { static void swap(Object A[], int i, int j){ Object o = A[i]; A[i] = A[j]; A[j] = o; } static void sort (Orderable A[]) {} static void printArray (Orderable A[]) { 3.2. GRUNDLEGENDE IMPLEMENTATION for (int i = 1; i < A.length; i++) System.out.print(A[i].toString()+" "); System.out.println(); } } 51 52 3.3 Auswahlsort KAPITEL 3. ELEMENTARE SORTIERVERFAHREN Idee: Bestimme der Reihe nach das i-kleinste Element im Rest der Liste (1 i n) und tausche es an die Position i Implementation class AuswahlSort extends SortAlgorithm { static void sort (Orderable A[]) { for (int i = 1; i < A.length-1; i++) { int min = i; for (int j = i+1; j <= A.length-1; j++) { if (A[j].less(A[min])) { min = j; } } swap(A,i,min); } } } Analyse: U bungsaufgabe 3.4. SORTIEREN DURCH EINFUGEN 53 3.4 Sortieren durch Einfugen class EinfuegeSort extends SortAlgorithm { static void sort (Orderable A[]) { for (int i = 2; i < A.length; i++) { Orderable temp = A[i]; int j = i - 1; while (j >= 1 && A[j].greater(temp)) { A[j+1] = A[j]; j--; } A[j+1] = temp; } } } Analyse von Insertion Sort Einfugen des i-ten Elementes benotigt mindestens 1, hochstens i 1 Vergleiche und mindestens 2, hochstens i + 1 Bewegungen (i = 2; : : : ; n) Cmin (n) = n 1 Cmax = n X i=2 (i 1) = n(n 1) 2 Mmin (n) = 2(n 1) n X Mmax (n) = (i + 1) = n(n2 1) + 2(n 1) i=2 Im Mittel: Die Halfte von s ; : : : ; si ist groer als si 0 1 Cav (n) Mav (n) nX1 i + 1 i=1 nX1 i i=1 2 2 = (n ) 2 1 + 2 = (n ) 2 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN 54 3.5 Shellsort Folge 1 = h < h < h < < hk 1. Zu hi > 0, j = 0; : : : ; hi 1, betrachte Teilfolgen sj ; sj h ; sj h ; : : : 2. Sortiere jeder dieser Teilfolgen durch Einfugen 3. Falls hi > 1, erniedrige hi auf einen Wert hi und gehe zu 1, sonst fertig; Satz: Worst case Laufzeit: O(n log n), falls hi 2 f2p3q < N g Satz: (Li and Vitanyi 1999): Average case fur alle Sequenzen (k pass) in (kn 1 + i 2 3 +2 i 1 2 3.5.1 Beispiel zu Shellsort h = 5, h = 3, h = 1 Eingabefolge: 15 2 43 17 3 2 1 h =5 3 h =3 2 h =1 1 15 2 43 17 4 8 47 4 8 47 8 2 43 17 4 15 47 8 2 43 17 4 15 47 8 2 15 17 4 43 47 8 2 15 17 4 43 47 2 4 3.5.2 Implementation: Shellsort class ShellSort extends SortAlgorithm { /* Sortierverfahren nach D.L. Shell */ static void sort (Orderable A[]) { int n = A.length - 1; int h = n / 2; 8 15 17 43 47 =k ). 1+1 3.5. SHELLSORT while (h > 1) { for (int j = 1; j <= h; j++) { sort (A, h, j); } h = h / 2; } sort(A, 1, 1); } static void sort (Orderable A[],int h,int k) { for (int i = k + h; i < A.length; i = i + h) { Orderable temp = A[i]; int j = i - h; while (j >= 1 && temp < A[j]) { A[j+h] = A[j]; j = j - h; } A[j+h] = temp; } } } 55 56 3.6 Mergesort KAPITEL 3. ELEMENTARE SORTIERVERFAHREN Mergesort|Sortieren durch Verschmelzen Prinzip von Mergesort Sortieren durch rekursives Verschmelzen von sortierten Teilfolgen Eingabe: unsortierte Folge L Ausgabe: sortierte Folge if |L| = 1 return L L1 = erste Haelfte von L L2 = zweite Haelfte von L Mergesort(L1); Mergesort(L2) return Merge(L1,L2) 3.6.1 Verschmelzen zweier Teilfolgen Merge Verschmelze die sortierten Folgen A[l..m] und A[m+1..r] zu einer Folge static void merge(Orderable A[],int l,int m,int r) { comparable B [] = new Orderable[A.length]; int i = l; // Zeiger in A[l],...,A[m] int j = m + 1; // Zeiger in A[m+1],...,A[r] int k = l; // Zeiger in B[l],...,B[r] while (i <= m && j <= r) { if (A[i].less(A[j])) { B[k] = A[i]; i++; } else { B[k] = A[j]; j++; } k++; } if (i > m) { // erste Teilfolge erschoepft for (int h = j; h <= r; h++, k++) B[k] = A[h]; } else { // zweite Teilfolge erschoepft for (int h = i; h <= m; h++, k++) B[k] = A[h]; } for (int h = l; h <= r; h++) A[h] = B[h]; } 3.6. MERGESORT 57 Beispiel: 2 4 8 15 17 25 43 47 62 9 12 15 16 28 33 37 Mergesort class mergeSort { /* sortiert das ganze Array */ public static void sort (Orderable A[]){ sort(A,1,A.length-1); } static void merge ... static void sort (Orderable A[], int l, int r){ if (r > l) { int m = (l + r) / 2; sort(A, l, m); sort(A, m+1, r); merge(A, l, m, r); } } } Beispiel: 8 6 7 3 4 5 2 1 divide 8 6 7 3 4 5 2 1 divide 8 6 7 3 4 5 2 1 divide 8 6 7 3 4 5 2 1 merge 3.6.2 Analyse von Mergesort Schlusselvergleiche fur Merge: KAPITEL 3. ELEMENTARE SORTIERVERFAHREN 58 worst-case: n + n 1 best-case: min(n ; n ) n =r m+1= n = m l = Lange der linken Teilfolge n = r m + 1 = Lange der rechten Teilfolge Rekursionsgleichung fur Laufzeit von Mergesort 1 2 1 2 2 1 2 T (n) = 2T (n=2) + O(n) O(n) = n=2 + n=2 1 worst case n=2 best case ) T (n) = n log n (n 1) zusatzlicher Speicher: O(n) 3.6.3 Reines 2-Wege-Mergesort Ansatz: for i = 0 to log n verschmelze Teilfolgen der Lange 2i 8 6 3 1 6 8 6 2 9 3 8 3 3 9 9 4 4 4 1 6 7 7 2 7 2 1 4 8 1 2 7 9 Naturliches 2-Wege-Mergesort Ansatz: Wie reines Mergesort mit Ausnutzung vorsortierter Teilfolgen 8 6 2 1 6 8 3 2 Cmin = n 1 9 9 4 3 3 2 6 4 4 3 7 6 7 4 8 7 2 7 9 8 1 1 1 9 Cav ; Cmax = O(n log n) 3.7. QUICKSORT 59 3.7 Quicksort Sortieren durch Teilen Divide-&-Conquer Prinzip Sehr gute Laufzeit im Mittel, schlechte Laufzeit im worst case. Quicksort Eingabe: unsortierte Liste L sortierte Liste if (|L| <= 1) return L else waehle Pivotelement p aus L L< = {a in L | a < p} L> = {a in L | a > p} return [Quicksort(L<)] + [p] + [Quicksort(L>)] Lin L< p Quicksort 3.7.1 Implementation Eingliederung in das Rahmenprogramm: class QuickSort extends SortAlgorithm { static void sort (Orderable A[]){ /* sortiert das ganze Array */ sort (A, 1, A.length-1); } p L> Quicksort 60 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN static void sort (Orderable A[], int l, int r){ /* sortiert das Array zwischen Grenzen l und r */ if (r > l) { // mind. 2 Elemente in A[l..r] int i = divide(A, l, r); sort (A, l, i-1); sort (A, i+1, r); } } static int divide (Orderable A [], int l, int r) { /* teilt das Array zwischen l und r mit Hilfe des Pivot-Elements in zwei Teile auf und gibt die Position des Pivot-Elementes zurueck */ ... } } Der Aufteilungsschritt divide(A; l; r): - liefert den Index des Pivotelements in A - ausfuhrbar in Zeit O(r l) 3.7. QUICKSORT l 3.7.2 Implementation: Aufteilungsschritt static int divide (Orderable A [], int l, int r) { // teilt das Array zwischen l und r mit Hilfe // des Pivot-Elements in zwei Teile auf und gibt // die Position des Pivot-Elementes zurueck r 61 62 int i = l-1; int j = r; KAPITEL 3. ELEMENTARE SORTIERVERFAHREN // linker Zeiger auf Array // rechter Zeiger auf Array Orderable pivot = A [r]; // das Pivot-Element while (true){ // "Endlos"-Schleife do i++; while (i < j && A[i].less(pivot)); do j--; while (i < j && A[j].greater(pivot)); if (i >= j) { swap (A, i, r); return i; // Abbruch der Schleife } swap (A, i, j); } } 3.7.3 Analyse von Quicksort Gunstigster Fall: log n Tmin (n) = O(n log n) Schlechtester Fall: 3.7. QUICKSORT 63 n Tmax (n) = O(n ) 2 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN 64 Best Case: Pivotelement teilt Folge jeweils genau zur Halfte auf Aufteilungsaufwand fur Folge der Lange n: n 1 Tmin (n) = Schusselvergleiche von Quicksort im besten Fall (Cmin ) Tmin(1) = 0 Tmin (n) 2Tmin(n=2) + n 1 ) Tmin (n) n log n (n 1) Worst Case: Eine der beiden durch Aufteilung entstehenden Folgen ist leer (z.B. aufsteigend sortierte Folge von Schlusseln) Tmax (n) = Schusselvergleiche von Quicksort im schlechtesten Fall (Cmax) Tmax (1) = 0 Tmax (n) = Tmax (n 1) + n 1 ) Tmax (n) = Tmax (n 2) + (n 2) + n 1 ... = T (1) + 1 + + (n 2) + (n 1) = n(n2 1) = O(n ) 2 3.7.4 Average Case Anahmen: 1. n paarweise verschiedene Schlussel 2. Alle n! Permutationen gleichwahrscheinlich Fakt: 3. Teilfolgen des Aufteilungsschritts wieder zufallig ) P (Pivotelement hat Rang k) = 1=n. Rekursionsgleichung fur mittlere (erwartete) Anzahl von Schusselvergleichen Tav (n) = E [C (n)] Tav (0) = 0 3.7. QUICKSORT 65 Tav (1) = 0 n X Tav (n) = n1 (Tav (k 1) + Tav (n k)) + n 1 k =1 66 KAPITEL 3. ELEMENTARE SORTIERVERFAHREN Kapitel 4 Schnelle Sortierverfahren 67 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 68 4.1 Kriterien fur Sortierverfahren Allgemeinheit, Einfachheit, In Situ{Eigenschaft (wenig Platz), Schlusselvergleiche schwer und grotmoglichste Anzahl der Vergleiche n log n + cn, ubrigen Operation c(n log n). 4.2. STATE-OF-THE-ART 69 4.2 State-of-the-Art Untere Schranke: Cmax(n) > Cav (n) dlog(n!)e 1 n log n 1:4427n, Ziel: Cmax(n) bzw. Cav (n) n log n + cn fur kleines c Quicksort-Varianten: QUICKSORT (Hoare 1962) Cav (n) 1:386n log n 2:846n + O(log n) CLEVER-QUICKSORT (Hoare 1962) Cav (n) 1:188n log n 2:255n + O(log n) QUICK-HEAPSORT (Cantone & Cincotti 2000) Cav (n) = n log n + 3n + o(n) QUICK-WEAK-HEAPSORT (U bung) Cav (n) = n log n + 0:2n + o(n) Heapsort-Varianten: HEAPSORT (Williams 1964) bzw. (Floyd 1964) Cmax(n) = 2n log n + O(n) BOTTOM-UP-HEAPSORT (Wegener 1993) Cmax(n) = 1:5n log n + O(n) allerdings Cav (n) = n log n + O(n) WEAK-HEAPSORT (Dutton 1993) Cmax (n) = n log n + 0:1n RELAXED-WEAK-HEAPSORT (U bung) Cmax (n) = n log n 0:9n 70 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 4.3 Average Case Analyse Quicksort Anahmen: - n paarweise verschiedene Schlussel - Alle n! Permutationen gleichwahrscheinlich Fakt: Teilfolgen des Aufteilungsschritts wieder zufallig ) P (Pivotelement hat Rang k) = 1=n. Rekursionsgleichung: Cav (0) = 0 Cav (1) = 0 n X Cav (n) = n 1 + n1 (Cav (k 1) + Cav (n k)) k =1 Satz (Geschlossene Form) Cav (n) 1:386n log n 2:846n + O(log n) Beweis: Exemplarisch losen wir die Rekursionsgleichung, wobei wir Cav durch C abkurzen. Durch Multiplikation mit n und Zusammenfassen der beiden Summanden (gleicher Indexbereich) gilt nC (n) = n(n 1) + 2 n X i=1 C (i 1) In dieser Gleichung kommen noch C (0); : : : ; C (n) vor. Um die Zahl verschiedener Werte auf 2 zu senken, betrachten wir die oben gewonnene Gleichung auch fur n 1 und subtrahieren diese Gleichung von der Gleichung fur n. (n 1)C (n 1) = (n 1)(n 2) + 2 nX1 i=1 C (i 1) Demnach ist nC (n) (n 1)C (n 1) = n(n 1) (n 1)(n 2) + 2C (n 1) Vereinfachung und Division durch (n + 1)n liefert 4.3. AVERAGE CASE ANALYSE QUICKSORT 71 C (n) = C (n 1) + 2(n 1) n+1 n n(n + 1) Es ist nun naheliegend, Z (n) = C (n)=(n + 1) zu untersuchen. Z (n) = Z (n 1) + n2((nn + 1) 1) = Z (n 2) + (2(nn 1)2)n + n2((nn + 1) 1) = : : : n i 1 X = Z (1) + 2 i i(i + 1) =2 Da C (1) = 0, ist Z (1) = 0. Es ist gut zu wissen, da 1 =1 1 i(i + 1) i i + 1 gilt. Also ist n i 1 X Z (n) = 2 i(i + 1) i n i 1 nX i 2 X = 2 2 i i i i n X = 1 + 2 1 2n 1 n+1 i i =2 +1 =2 =3 =3 Die Reihe 1+ + + + : : : tritt so haug auf, da sie einen Namen, namlich Harmonische Reiche H (n), erhalten hat. Es folgt Z (n) = 2H (n) 2 2 nn + 11 = 2H (n) 4 + n +4 1 und 2 C (n) = (n + 1)Z (n) = 2(n + 1) H (n) 2 + n + 1 = 2(n + 1)H (n) 4n 1 2 1 3 1 4 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 72 Was konnen wir uber H (n) aussagen? Aus der Betrachtung der Riemannschen Summen folgt Zn 1 Zn1 H ( n ) x x und damit +1 1 1 ln(n + 1) H (n) 1 + ln n Ohne groere Probleme lat sich sogar zeigen, da H (n) ln n konvergiert. Der Grenzwert heit Eulersche Konstante, es gilt 0:57721 : : : Schlielich ist ln n = (log n) ln 2. Insgesamt gilt also C (n) = 2(n + 1)H (n) 4n 1:386n log n 2:846n + O(log n) Gleiche Analyse wie Randomisiertes Quicksort - Pivot = zufalliges Element aus A[l::r] - vertausche A[r] mit Pivot - weiter wie bisher Worst-case: Laufzeit jetzt zufallig Cex(n) 4.4. CLEVER-QUICKSORT 73 4.4 Clever-Quicksort Auswahl des Pivotelementes (Median-of-3 Strategie): - a) m = (l + r)=2 oder b) m = l + 1 - Pivot: mittleres Element von fA[l]; A[m]; A[r]g - vertausche A[r] mit Pivot - weiter wie bisher Worst-case: - verschwindet in a) fur auf- bzw. absteigende Sortierung - existiert immer noch Bemerkung: Median von 3 Objekte kann in durchschnittlich 8/3 Vergleichen gefunden werden. ) Divide im Mittel n 3 + 8=3 = n 1=3 Vergleiche. Wahrscheinlichkeit, da x an der Position k steht, ist gleich (k 1)(n k)= n , da k 1 Positionen fur das kleinere und n k Positionen fur das groere Objekt. 3 8 0, fur n 2 f0; 1g > > < 1, fur n = 2 Pk n Cav (n) = > > : n + =1 n ( 1 3 k 1)( k)(V (k V (n k)) 1)+ (3) n Satz: Schlusselvergleiche im Mittel Cav (n) 1:188 n log n 2:255 n + O(log n) 4.4.1 Implementation Bemerkung: Siehe Sedgewick: The analysis of quicksort programs, Acta Informatica, Journal of Algorithms, 15(1):76-100, 1993 static void sort(Orderable A[], int left,int right) { if (right-left >= 3) { if(A[right].less(A[left+1])) swap(A,left+1,right); if(A[right].less(A[left])) swap(A,left,right); if(A[left].less(A[left+1])) swap(A,left+1,left); int i = left+1, j = right; Orderable v = A[left]; do { do { i++; } while (A[i].less(v)); KAPITEL 4. SCHNELLE SORTIERVERFAHREN 74 do { j--; } while (v.less(A[j])); if (j >= i) swap(A,i,j); } while(j >= i); swap(A,left,j); if (j-left < right-i+1) { sort(A,left,j-1); sort(A,i,right); } else { sort(A,i,right); sort(A,left,j-1); } } else threesort(A,left,right); } } 4.5. HEAPSORT 75 4.5 Heapsort Ezientes Sortieren durch Auswahlen Prinzip von Heapsort: Sortieren durch wiederholtes Auswahlen des Maximums (Auswahlsort). Verwende Struktur (Heap), die Bestimmung des Maximums ezient unterstutzt. Verwandle unsortierte Folge F in einen Heap while (F <> {}) gebe maximales Element von F aus entferne maximales Element aus F und verwandle Restfolge wieder in einen Heap Denition: Folge F = (k ; k ; : : : ; kn) von Schlusseln heit Heap, wenn fur alle i = 1; 2; : : : ; n=2 gilt: ki k i und ki k i 1 2 2 2 +1 1 2 3 4 5 6 7 47 17 43 15 8 4 2 47 15 17 8 43 4 2 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 76 4.5.1 Veranschaulichung 1 2 3 4 5 6 7 47 17 43 15 8 4 2 1 2 4 3 5 6 7 Vollstandiger Binarbaum mit Positionsnummern: - Level i hat 2i Knoten (auer dem letzten Level) - Knoten von oben nach unten und von links nach rechts numeriert - Knoten i hat Knoten 2i als linken und Knoten 2i + 1 als rechten Sohn Heapbedingung: - Schlussel(p) Schlussel(pl ) - Schlussel(p) Schlussel(pr ) Maximum ist an der Wurzel (Position 1) 4.5.2 Entfernen des Maximums 1. Entferne k 1 4.5. HEAPSORT 77 1 43 17 15 8 2 4 2. Ubertrage kn an die Wurzel 1 17 15 43 8 4 3. Versickere k 1 1 17 15 4.5.3 Versickern Allgemeiner: versickere ki in i; : : : ; l 8 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 78 ki k2i k2i+1 i 2i 2i + 1 l class HeapSort extends SortAlgorithm { static void pushdown(Orderable A[],int i,int n) { while (2*i <= n) { // i hat linken Sohn int j = 2*i; if (j < n && A[j].less(A[j+1])) j = j + 1; // 2i + 1 ist groesserer Sohn if (A[i].less(A[j])) { swap(A,i,j); i = j; // versickere weiter } else i = n; // exit loop } } ... } 4.5.4 Erstellen eines Heaps Beobachtung: Die Elemente A[n=2]; : : : ; A[n] erfullen bereits die Heap-Bedingung 4.5. HEAPSORT 79 1 15 2 2 4 17 5 4 3 43 6 8 public static void heapify (Orderable A[]) { // verwandle A in einen Heap int n = A.length-1; for (int i = n/2; i >= 1; i--) { pushdown(A,i,n); } } A uere Schleife public static void sort (Orderable A[]){ heapify(A); // generiere Heap for (int i = A.length-1; i >= 2; i--) { swap(A,1,i); pushdown(A,1,i-1); // versickere Wurzel } } 7 47 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 80 4.5.5 Iteriertes Versickern 20 21 2k 2 2k 1 n = 2k 1 (k 1) Tiefe # Elemente # Vergleiche 0 2k =2 0 k 1 2 =2 21 k 2 2 =2 22 kX i ... ... ... Gesamtaufwand = 2k 2 2i i i 2k =2i 2i ... ... ... k 1 2k =2k 2(k 1) 1 0 1 1 1 2 1 =0 1 1 1 4.5.6 Analyse Wissen: 1 Falls A Heap mit n Schlusseln max(A) : 1, deletemax(A) : 2 log n (2 Vergl. pro Niveau) heapify(A) : 2n 4.5. HEAPSORT 81 Insgesamt: Laufzeit : O(n log n), genauer: 2n log n + 2n Schlusselvergleiche Platzbedarf : n fur das Eingabearray + O(1) Schlimmster Fall in Heapsort: Es wird immer das kleinste Element versickert Beobachtung: Die erwartete Tiefe eines versickerten Elementes im Heap ist gro. Idee Bottom-up-Heapsort: - Bestimme nur groeren der beiden Sohne mit einem Schlusselvergleich pro Niveau - sinke immer bis zu einem Blatt (search-leaf) - steige dann wieder auf (bottom-up-search) 4.5.7 Bottom-up-Heapsort 15 14 12 8 13 11 4 2 10 1 7 3 5 6 15 14 8 12 11 5 13 1 10 2 4 7 3 6 KAPITEL 4. SCHNELLE SORTIERVERFAHREN 82 15 14 9 12 1 11 8 13 10 2 4 4.5.8 Bottom-Up-Heapsort: Implementation Versickern: void pushdown(int root,int m) { int j = LeafSearch(root,m); j = BottomUpSearch(root,j); Interchange(root,j); } Suche speziellen Weg: static int LeafSearch(Orderable A[],int root,int m) { int i = 0, j = Path[i++] = root; while((2*j)<m) { if (A[2*j+1].less(A[2*j])) Path[i++] = j = 2*j; else Path[i++] = j = 2*j+1; } if(2*j==size) j = Path[i++] = m; return j; } Suche Einsinkposition: static int BottomUpSearch(Orderable A[],int i,int j){ while(j>i && A[j].less(A[i])) j /= 2; return j; } 7 5 3 6 4.5. HEAPSORT 83 Ringtausch ezienter als iteriertes Vertauschen: static void Interchange(Orderable A[],int i,int j){ int k=0; Orderable v = A[Path[0]]; for(;Path[k]<j;k++) A[Path[k]] = A[Path[k+1]]; A[Path[k]] = v; } 4.5.9 Ergebnisse Satz: Bottom-up Heapsort fuhrt zum Sortieren einer Folge von n Schlusseln im schlechte- sten Fall nur 1:5n log n + (2 c(n))n + O(log n) Schlusselvergleiche aus (Wegener 1993). Fleischer (1991) sowie Schaer und Sedgewick (1993) haben worst-case Beispiele angegeben, bei denen die Anzahl der wesentlichen Vergleiche fur BOTTOM-UP-HEAPSORT gleich 1:5n log n o(n log n) ist. Satz: Im Mittel benotigt Bottom-up Heapsort (nur) n log n + O(n) Schlusselvergleiche (Li & Vitanyi 1993). 2 Experimente: Sei d(n) so gewahlt, da n log n + d(n)n die erwartete Anzahl an Schlusselvergleichen von BOTTOM-UP-HEAPSORT ist. Dann liegt d(n) im Intervall von [0.34,0.39]. Diese Zahl ist gro fur n 2k und klein fur n 1:4 2k . KAPITEL 4. SCHNELLE SORTIERVERFAHREN 84 4.6 WEAK-HEAPSORT Die Datenstruktur Weak-Heap ... 75 70 35 6 32 10 0 36 11 zerfallt in Substrukturen: 1 T Th 2 T1 T2 k Tk 4.6.1 Verschmelzen Merge ( Knoten x , durch y beschriebener Baum ): 5 4.6. WEAK-HEAPSORT a) 85 b) x y x y lT rT rT Voraussetzung: (x; lT (y)), (y; rT (y)) Weak{Heaps. Fall a) a[x] > a[y], Fall b) a[x] a[y]. Satz: Merge liefert einen Weak{Heap und benotigt einen Vergleich. Achtung: Teilbaumrotation mu realisiert werden. 4.6.2 Generierung eines Weak-Heaps Heapify : lT KAPITEL 4. SCHNELLE SORTIERVERFAHREN 86 Zur Initialisierung des Weak-Heaps wird jeder Knoten j mittels Merge mit seinem Groelternteil Gparent(j ) verbunden (bottom{up). Satz: Heapify liefert in n 1 Vergleichen einen Weak-Heap. 4.6.3 Sortierungsphase MergeForest ( derzeitige Groe m ): m’ m’ 1 2 m’ T1 T2 m’ k x Tk m Satz: MergeForest liefert in einen Weak{Heap{Wald einen neuorganisierten Weak{Heap. 4.6.4 Arrayeinbettung Problem der Arrayeinbettung: 1. Die Wurzel root(T ) wird auf die Arrayposition 0 abgebildet. 2. Falls der Knoten v 2 T auf die Arrayposition x abgebildet wird, so wird lchild(v) auf die Position 2x + Reverse[x] und rchild(v) auf die Position 2x + 1 Reverse[x] abgebildet. 4.6. WEAK-HEAPSORT 87 75 a) b) 70 35 32 10 x 75 70 6 8 11 35 36 4 m 5 32 1 10 m 6 x 0 36 5 11 75 c) 70 35 x 32 6 0 36 5 m In den Fallen b) und c) liegen m und x auf unterschiedlichen Leveln, d.h. depth(x) = depth(m) 1 = dlog(m + 1)e 1 In Fall a) gilt hingegen depth(x) = depth(m) = dlog(m + 1)e. 11 4 4.6.5 Implementation static int Gparent(int j) { while ((j & 1) == 0) j /= 2; return (j / 2); } static void Merge(int i,int j, Orderable A[], boolean Reverse[]) { if (A[i].less(A[j])) { swap(A,i,j); Reverse[j] = !Reverse[j]; } } static void MergeForest(int m, Orderable A[], boolean Reverse[]) { int x=1; while ((2*x + (Reverse[x]?1:0)) < m) x = 2*x + (Reverse[x]?1:0); do { Merge(m,x,A,Reverse); x /= 2; } while (x>0); } static void sort(Orderable A[]) { KAPITEL 4. SCHNELLE SORTIERVERFAHREN 88 for(int i=1; i < A.length; i++ ) A[i-1] = A[i]; boolean Reverse [] = new boolean[A.length]; for (int i = A.length-2; i >= 1; i--) Merge(Gparent(i),i,A,Reverse); A[A.length-1] = A[0]; for (int i = A.length - 2; i >= 2; i--) MergeForest(i,A,Reverse); } 4.6.6 Laufzeitanalyse Satz: Sei k = dlog ne. Die Anzahl der Vergleiche in WEAK-HEAPSORT is durch nk 2k + n 1 n log n + 0:086013n beschrankt. Beweis: Die Aufrufe von MergeForest(i) benotigen hochstens Pni dlog(i +1)e = nk 2k 1 =2 Vergleiche. Zusammen mit den n 1 Vergleichen um den Weak-Heap aufzubauen haben wir also insgesamt nk 2k + n 1 Vergleiche. Fur alle n ndet sich ein x in [0; 1] with nk 2k + n 1 = n log n + nx n2x + n 1 = n log n + n(x 2x + 1) 1. Einfache Analysis (Ableitungen) zeigt, da die Funktion f (x) = x 2x + 1 ihr Maximum an x = ln ln 2= ln 2 mit Funktionswert f (x ) = 0:086013 annimmt. Experimente: Sei d(n) so gewahlt, da n log n + d(n) die erwartete Anzahl von Vergleichen fur WEAK{HEAPSORT ist. Dann ist d(n) 2 [ 0:47; 0:42]. Weiterhin ist d(n) klein fur n 2k und gro fur n 1:4 2k . 0 0 4.7. QUICK-HEAPSORT 89 4.7 Quick-Heapsort Idee: Hybrid-Algorithmus, verbindet den Divide-and-Conquer Ansatz von QUICKSORT mit HEAPSORT. Trick: Aufteilung des Elementarrays in umgekehrter Richtung, d.h. A[1::j 1] groer als Pivot A[j ] und A[j + 1::n] kleiner-gleich dem Pivot an j . Einseitiges Heapsort: EXTERNAL-HEAPSORT wird fur das kleinere Elementarray aufgerufen. Ist dies der erste Teil so konstruiert QUICK-HEAPSORT einen Max-, sonst einen Min-Heap. Versickerung in EXTERNAL-HEAPSORT: Das Wurzelelement wird jeweils durch das kleinere Kind ersetzt (1 Vergleich) und an die Endposition in den jeweils anderen Teilbereich geschrieben (Tausch). Rekursion: Cav (n) Mittel von EXTERNAL-HEAPSORT. Cav (1) = 0, Cav (2) = 1 und Cav (2n) = nP 2n + 1 + n nj Cav (j 1) +o Cav (2n j )+ P n C (2n j ) + C (j 1) av j n av Satz: Im Mittel sortiert QUICK-HEAPSORT n Elemente in n log n + 3n + o(n) Vergleichen. 1 2 =1 2 = +1 4.7.1 EXTERNAL-HEAPSORT a) Fulle Lucke in Max-Heap mit jeweils kleinerem Kind und gebe resultierendes Blatt zuruck static int max_special_leaf(Orderable A[], int left, int right) { int i = left+1; // left son of root while( i<right ) { if (A[i].less(A[i+1])) i++; A[left + (i-left+1)/2 - 1] = A[i]; i = left + 2*(i-left+1) - 1; } if (i == right) { A[left+(i-left+1)/2 - 1] = A[i]; i = left + 2*(i-left+1) - 1; } return left + (i-left)/2; } b) Sortiere A in den Grenzen I = [heapleft::heapright] hinein in den Bereich [workright jI j + 1::workright] 90 KAPITEL 4. SCHNELLE SORTIERVERFAHREN static void external_maxheap_sort(Orderable A[], int heapleft, int heapright, int workright) { build_max_heap(A, heapleft, heapright); for(int j=heapright; j>=heapleft; j--) { Orderable tmp = A[workright]; A[workright--] = A[heapleft]; int l = max_special_leaf(A, heapleft, heapright); A[l] = tmp; } } 4.7.2 Implementation: QUICK-HEAPSORT c) Der Aufteilungsschritt: static void quickheap(Orderable A[], int left, int right) { while (right-left > 0) { int i = left, j = right+1; Orderable v = A[left]; do { do j--; while (j>=i && A[j].less(v)); do i++; while (i<=j && v.lessEqual(A[i])); if (j > i) swap(A,i,j); } while(j >= i); swap(A,left, right - (j-left)); if( (j-left) >= (right-j) ) { external_minheap_sort( A, j+1, right, left ); left = right - (j-left) + 1; } else { external_maxheap_sort( A, left+1, j, right ); right = right - (j-left) - 1; } } } d) Sortierung im Bereich A[1::n]: public static void sort(Orderable A[]) { quickheap(A,1,A.length-1); } Kapitel 5 Spezielle Sortierverfahren 91 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN 92 5.1 Radixsort Idee: Nutzt Zahldarstellung der Schlussel anstelle von Vergleichen Schlussel k Wort uber Alphabet mit m Elementen Xl k = (kl ; kl ; : : : ; k ; k )m = ki mi i ki 2 f0; : : : ; m 1g m = 10 Dezimalzahlen m = 2 Binarzahlen m = 26 Zeichenketten uber fa; : : : ; zg Funktion: zm (k; i) = ki 1 1 0 =0 5.1.1 Radix-exchange-sort Sortieren durch rekursive Aufteilung nach hochstwertigen Ziern (Bits) Eingabe: Folge F von binaeren Schluesseln mit Bits an Positionen 0,...,l, Ausgabe: Nach Bitpositionen sortierte Folge if (|F| = 1 || b < 0) return F Sortiere F nach Bit b (Quicksort) F0 = {k in F | z_2(b,k) = 0} F1 = {k in F | z_2(b,k) = 1} return Radix-exchange-sort(F0,b-1) + Radix-exchange-sort(F1,b-1) Beispiel: F = (6; 7; 4; 2; 3) { (110; 111; 100; 010; 011) 5.1.2 110 111 100 010 b = 2 011 010 100 111 b = 1 011 010 100 111 b = 0 010 011 110 Problem: Informationsgewinn pro Operation nur ein Bit Radix-exchange-sort 011 110 110 111 Denition: Die unterscheidenen Praxe einer Menge fa ; : : : ; ang von Zeichenketten sind die kurzesten Praxe von fa ; : : : ; ang, die paarweise verschieden sind. 1 1 Der unterscheidende Prax einer Zeichenkette ai, die Prax einer anderen Zeichenkette ist, ist ai selbst. Beispiel 5.1. RADIXSORT 93 SOCKEL SOCRATES SODA SOFORT SOFA SORT SORTIEREN si = unterscheidender Prax von ai S= n X i=1 jsij 5.1.3 Analyse von Radix-exchange-sort Aufteilung bei Radix-exchange-sort: F00 F0 F F10 F01 F1 Laufzeit von Radix-exchange-sort: - konstanter Aufwand pro Aufteilungsschritt fur ai (Bit b testen, tauschen) - # Aufteilungsschritte fur ai = jsij T (a ; : : : ; an) = 1 n X i=1 jsij = O(n + S ) Anders: T (a ; : : : ; an) = jf(ai ; A) j ai beteiligt an Aufteilungsschritt Agj n X = jfA j ai beteiligt an Aufteilungsschritt Agj 1 i=1 F11 94 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN 5.1.4 Implementation: Decomposable Beschreibt das Verhalten von Objekten, bei denen auf eine Zier zugegrien werden kann abstract class decomposable { // Wertebereich der Zeichen: 0 .. range - 1 public int range () { return 0; } // Index eines Zeichens an Stelle i public abstract int zeichen (int i); // Anzahl der Zeichen des Objektes public abstract int laenge (); public abstract void printArray(decomposable arr[],int l,int r); } 5.1.5 Implementation: Ganzzahlen class decomposableInt extends decomposable { protected int i ; // key // hier koennte noch ein Info-typ kommen decomposableInt (int i){ this.i = i; } public int range () { return 2; } // Bitstring // gib Bit i zurueck (0-tes Bit steht ganz rechts) public int bit(int stelle){ int mask = 1 << stelle; if ((mask & this.intValue()) == 0) return 0; return 1; } // gib Zeichen i zurueck public int zeichen (int stelle){ return this.bit(31 - stelle); } public int laenge () { return 32; } // Gibt die variable Laenge zurueck public int varLaenge () { return (int) Math.round(Math.log(this.intValue()) / Math.log(2.0) + 0.5); } int intValue() { return i; } // Gibt Key zurueck static void printArray ... } 5.1. RADIXSORT 5.1.6 Implementation: Zeichenketten class decomposableString extends decomposable { protected String s ; // key // hier koennte noch ein Info-typ kommen decomposableString (String s){ this.s = s; } // Wir betrachten Zeichen mit Unicode in [0 .. 127] public int range () { return 128; } // gib Zeichen i zurueck falls vorhanden public int zeichen (int stelle){ if (stelle < s.length ()) return new Character(s.charAt(stelle)).hashCode()%128; return -1; } public int laenge() { return s.length (); } public void printArray(decomposable arr[],int l,int r) { for (int i = l ; i <= r; i++) System.out.print(arr[i].string()+" "); System.out.println (); } } 5.1.7 Implementation: Radix-Exchange class radixExchangeSort { /* sortiert das ganze Array */ static void sort (decomposable A[]){ int n = A.length-1; int max = 1; for (int i = 2; i <= n; i++) { if (A[i].intValue () > A[max].intValue ()) max = i; } sort (A, 1, n, A[max].varLaenge ()); } /* sortiert das Array zwischen den Grenzen l und r abhaengig von Bit b */ static void sort (decomposable A[], int l, int r, int b){ 95 96 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN if (r > l && b >= 0){ int i = l-1; // linker Zeiger auf Array int j = r+1; // rechter Zeiger auf Array while (true){ // "Endlos"-Schleife do i++; while (i <= r && A[i].bit(b) == 0); do j--; while (j >= l && A[j].bit(b) == 1); if (i >= j) break; // Abbruch der Schleife swap (A, i, j); } sort (A, l, i-1, b-1); sort (A, i, r, b-1); } } } 5.2. SORTIEREN DURCH FACHVERTEILUNG 97 5.2 Sortieren durch Fachverteilung Zuerst nach dem letzten Zeichen sortieren Beispiel: F = 434; 528; 154; 176; 783; 204; 351; 218; 900 Verteilen nach Zier 3: 900 351 F0 F1 F2 204 154 783 434 F3 F4 F5 176 F6 F7 218 528 F8 F9 Sammeln: 900, 351, 783, 434, 154, 204, 176, 528, 218 204 Verteilen nach Zier 2: 900 218 528 434 F0 F1 F2 F3 F4 154 351 F5 F6 176 783 F7 F8 F9 Sammeln: 900, 204, 218, 528, 434, 351, 154, 176, 783 Verteilen nach Zier 1: F0 176 218 154 204 351 434 528 F1 F2 F3 F4 F5 F6 783 F7 154, 176, 204, 218, 351, 434, 528, 783, 900 5.2.1 Analyse Aufwand pro Durchlauf: l = Lange der Zeichenketten in Bits Anzahl Durchlaufe: l= log m Aufwand pro Durchlauf: O(n + m) ) Gesamtaufwand: O((n + m) l= log m) Speicherplatz: O(n + m) Vergleich: Operationen Radix-exchange-sort O(n + S ) O((n + m) l= log m) Fachverteilung Vergleich der Aufteilungen Radix-exchange-sort: F8 900 Sammeln: F9 98 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN Sortieren durch Fachverteilung: 5.2.2 Implementation: Fachverteilung class Fachverteilung { static void sort (decomposable A[]) { 5.2. SORTIEREN DURCH FACHVERTEILUNG int n = A.length-1; int m = A[0].range (); list fach [] = new list [m+1]; // Initialisiere die Faecher for (int i = 0; i <= m; i++) { fach[i] = new list (); } // Berechne die max. Laenge einer Zeichenkette int max = 1; for (int i = 2; i <= n; i++) { if (A[i].laenge () > A[max].laenge ()) max = i; } // Sortiere die Zeichenketten for (int z = A[max].laenge () - 1; z >= 0; z--) { /* Verteile A[1] .. A[n] nach Zeichen z auf fach[1] .. fach[m]; fach[0] enthaelt Zeichenketten, die kein z-tes Zeichen haben */ for (int i = 1; i <= n; i++) fach [A[i].zeichen (z) + 1].append (A[i]); /* Sammle die Zeichen wieder ein */ int i = 0; for (int j = 0; j <= m; j++) { while (! fach[j].isEmpty()) { A[++i] = fach[j].removeFirst(); } } } } } 99 100 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN 5.3 Das Auswahlproblem Problem: Finde das i-kleinste Element in einer Liste F mit n Elementen Naive Losung j = 0 while (j < i) bestimme kleinstes Element a_{min} entferne a_{min} aus F; j = j + 1; return a_{min} Anzahl der Schritte: O(i n) fur i = n=2 (Median): O(n ) (Sortieren ist besser) Verfahren mit Heap 2 verwandle F in einen min-Heap j = 0; while (j < i) a_{min} = delete-min(F); j = j + 1; return a_{min} Anzahl der Schritte: O(n + i log n) fur i = n=2 (Median): O(n log n) 5.3.1 Implementation (Rahmen) Basisklasse: class SelectAlgorithm { static void swap (Object A[], int i, int j) { Object o = A[i]; A[i] = A[j]; A[j] = o; } static void select (Orderable A[], int i) { IthElement.select(A, i); } static void printArray (Orderable A[]) { for (int i = 1; i < A.length; i++) System.out.print(A[i].toString()+" "); System.out.println(); } } 5.3. DAS AUSWAHLPROBLEM In IthElement 101 : extends SelectAlgorithm public static Orderable select(Orderable A[],int i){ // Suche das i-groesste Element in A[1],...,A[n] int n = A.length - 1; A [0].minKey(); // Stopper if (i <= n) return A[selectIndex (A, i, 1, n)]; return A[0]; } 5.3.2 Divide-&-Conquer Losung Idee: Aufteilung von F = (a ; : : : ; an) in zwei Gruppen bzgl. Pivotelement p (Quicksort) 1 L< Lin p Quicksort p L> Quicksort public static int selectIndex (Orderable A[],int i,int l,int r) { // Suche den Index des i-groessten Elementes // in A[l],...,A[r] if (r > l) { int p = pivotElement(A, l, r); int m = Quicksort.divide(A, l, r, p); if (i <= m - l) return selectIndex (A, i, l, m - 1); return selectIndex (A, i - (m - l), m, r); } else return l; } Nur eine der zwei durch Aufteilung entstandenen Folgen wird weiter betrachtet. KAPITEL 5. SPEZIELLE SORTIERVERFAHREN 102 5.3.3 Wahl des Pivotelements 1. p = ar folgt T (n) T (n 1) + O(n) Laufzeit im schlimmsten Fall: O(n ) 2 Beispiel: Auswahl des Minimums in aufsteigend sortierter Folge 2. Randomisiert p = ein zufalliges Element aus a ; : : : ; an erwartete Laufzeit: (U bung) 1 3. Median-of-Median Input: eine Folge F = (a ; : : : ; an) Output: ein Element a 2 F , so da - jfaj j aj > agj 3(d d n ee 2) 3n=10 6 - jfaj j aj > agj 3(d d n ee 2) 3n=10 6 ) Absplitten eines konstanten Teils = 7dn=10e + 6 1 1 2 1 2 5 5 5.3.4 Median-of-Median Algorithmus: 1) Unterteile F in 5er-Gruppen G ,. . . ,Gdn= e 2) 8j 2 f1; : : : ; dn=5eg sort (Gj ) 3) 8j 2 f1; : : : ; dn=5eg b[j ] Median (Gj ) 4) return select (b; dn=10e; 1; dn=5e) S (n) = # Vergleiche Select G(n) + n + S (d7n=10 + 6e) G(n) = # Vergleiche Median-of-Median c00 n + S (dn=5e) S (n) S (d7n=10 + 6e) + S (dn=5e) + c0n Satz: S (n) cn 1 5 5.4. SORTIEREN VORSORTIERTER DATEN 5.4 Sortieren vorsortierter Daten Spezielle Eingaben treten hauger auf, Daten sind vorsortiert Vorsortierungsmae: 1. Anzahl der Inversionen inv(F ) = jf(i; j ) j 1 j < i n; kj > kigj Beispiel: 15 2 43 17 4 8 47 3 Inversionen, an denen 15 beteiligt ist; 3 + 3 + 2 = 8 Inversionen F aufsteigend sortiert: inv(F ) = 0 nX F absteigend sortiert: inv(F ) = i = n(n2 1) i inv mit die globale Vorsortierung Beispiel: n=2 + 1; : : : ; n; 1; : : : ; n=2 1 =1 2. Anzahl aufsteigend sortierter Teilfolgen runs(F ) = jfi j 1 i n; ki > ki gj + 1 +1 = Anzahl aufsteigend sortierter Teilfolgen Beispiel: F : 15 2 43 17 4 8 47 runs(F ) = 4 F aufsteigend sortiert: runs(F ) = 1 F absteigend sortiert: runs(F ) = n runs mit die lokale Vorsortierung 3. Langste aufsteigende Teilfolge las(F ) = Lange der longest ascending subsequence(F ) = maxft j 91 i < < it n; ki1 ki2 ki g 1 t 1 las(F ) n Beispiel: F : 15; 2; 43; 17; 4; 8; 47, las(F ) = 4 rem(F ) = n las(F ) F aufsteigend: rem(F ) = 0 absteigend: rem(F ) = n 1 103 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN 104 5.4.1 Optimale Nutzung der Vorsortierung m-optimales, allgemeines Sortierverfahren Ziel: fur jeden Wert m von m soll nur die fur Folgen dieses Vorsortiertheitsgrades notige Schrittzahl verwendet werden. Untere Schranke fur Anzahl der notigen Schlusselvergleiche Cm0 ? Anzahl der Blatter im Entscheidungsbaum mit m(F ) m 0 0 jfF jm(F ) m gj: 0 ) Denition Cm0 log jfF jm(F ) m gj 0 Ein Sortierverfahren A heit m-optimal, falls es eine Konstante c gibt, so da fur alle n und alle Folgen F mit Lange n die Zeit TA(F ) zum Sortieren von F mit A wie folgt beschrankt ist: TA (F ) c (n + log jfF 0j m(F 0) m(F )gj): ALLGEMEINE SORTIERVERFAHREN 5.5. UNTERE SCHRANKE FUR 105 5.5 Untere Schranke fur allgemeine Sortierverfahren Satz: Zum Sortieren einer Folge von n Schlusseln mit einem allgemeinen Sortierverfahren sind im Worst-case dlog n!e = (n log n) Vergleichsoperationen zwischen zwei Schlusseln erforderlich. Im Mittel benotigt man wenigstens dlog n!e 1 = (n log n) viele Vergleiche. Bemerkung: p n = n Nach der Stirling-Formel konvergiert der Quotient von n! und 2n e gegen 1. Daher ist +1 2 log n! n log n n log e + O(log n) n log n 1:4427n Modellierung von allgemeinen Sortierverfahren Entscheidungsbaume. 5.5.1 Entscheidungsbaume Sortierverfahren A Entscheidungsbaum TA;n zur Modellierung von des Ablaufs von A auf Folgen der Lange n enthalt: - fur jede der n! Permutationen ein Blatt - innere Knoten repasentieren eine Vergleichsoperation und haben zwei Sohne - Weg W von der Wurzel zu einem Blatt v: - die Vergleiche an den Knoten von W identizieren die Permutation v von v - entsprechen den von A durchgefuhrten Vergleichen, falls die Eingabe v ist Beispiel: Sortieren durch Einfugen fur Folge F =<k ; k ; k ; k > von 4 Schlusseln 1 2 3 4 1:4 AA 1423 4123 A 1:4 L 1432 4132 PP P 1:3 b " " b " b PP PP 3:4 L 4312 AA 2:4 L 4213 A AA 2413 2143 L 1:4 " " L L 3:4 2134 A 3412 3142 L 1:4 L L 2:4 @ @ 3124 @ AA 1:3 A L 3:4 L 1342 1324 L L L b b b b 2:4 1243 b 2:4 L L 1234 3:4 " " " " " 2:3 b b 2:3 @ @ @ 3:4 2:4 2431 1:4 2:4 3:4 4321 A AA L L 3421 L L 3241 3214 4231 A AA L L 2341 L L 2314 1:4 PP 1:2 PP 106 KAPITEL 5. SPEZIELLE SORTIERVERFAHREN Kapitel 6 Suchverfahren und Suchbaume 107 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 108 6.1 Problemstellung Problem: Gegeben Folge F = (a ; : : : ; an). Finde das Element mit Schlussel k. Rahmen: 1 class SearchAlgorithm { static int searchb(Orderable A[], Orderable k) { return search(A,k); } } Einfachstes Verfahren: Sequentielle, lineare Suche class SequentialSearch extends SearchAlgorithm { public static int search(Orderable A[],Orderable k){ /* Durchsucht A[1], .., A[n] nach Element k und liefert den Index i mit A[i] = k; -1 sonst */ A[0] = k; // Stopper int i = A.length; do i--; while (!A[i].equal(k)); if (i != 0) // A[i] ist gesuchtes Element return i; else // es gibt kein Element mit Schluessel k return -1; } } Analyse: - schlechtester Fall: n+1 n X 1 - im Mittel: n i = n1 n(n2+ 1) = n +2 1 i=1 SUCHE 6.2. BINARE 6.2 Binare Suche Klasse class BinarySearch extends SearchAlgorithm Hauptroutine public static int search(Orderable A[],Orderable k){ /* Durchsucht A[1], .., A[n] nach Element k und liefert den groessten Index i >= 1 mit A[i] <= k; 0 sonst */ int n = A.length; return search(A, 1, n, k); } Rekursiver Aufruf public static int search (Orderable A[], int l, int r, Orderable k){ /* Durchsucht A[1], .., A[n] nach Element k und liefert den groessten Index l <= i <= r mit A[i] <= k; l-1 sonst */ if (l > r) // Suche erfolglos return l-1; int m = (l + r) / 2; if (k.less(A[m])) return search(A, l, m - 1, k); if (k.greater(A[m])) return search(A, m + 1, r, k); else // A[m] = k return m; } 6.2.1 Binare Suche ohne Rekursion public static int search(Orderable A[],Orderable k){ int n = A.length, l = 1, r = n; while (l <= r) { int m = (l + r) / 2; if (k.less(A[m])) { r = m - 1; } else if (k.greater(A[m])) { l = m + 1; } else return m; 109 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 110 } return l-1; } Annahme: Binarer Vergl.-Operator mit 3 Ausgangen Worst Case (n = 2k 1): bei k = log(n + 1) Vergl. Average Case (n = 2k 1): 8 12 4 2 1 10 6 3 5 7 14 11 9 13 15 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 6.2.2 Analyse Auswertung: Pki i2i =1 1 1 2k = ... = 1 2 + : : : + 1 2k = 1 2 + 1 2 + : : : + 1 2k = = 0 1 1 1 1 1 2k ... 2k 2k k2k 2k 2 1 1 2k + 1 SUCHE 6.2. BINARE 111 Erwartungswert: k X E = ( i2i )=n i=1 (k2k 1 = 2k + 1)=n = ((n + 1) log(n + 1))=n (n + 1)=n + 1=n = (n + 1) log(n + 1)=n log(n + 1) 1 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 112 6.3 Fibonacci-Suche Erinnerung: F = 0; F = 1; Fn = Fn + Fn 0 1 1 Verfahren: fur (n 2): 2 Vergleiche den Schlussel an i = Fn mit k. - A[i]:key > k: Durchsuche linke Fn 1 Elemente - A[i]:key < k: Durchsuche rechte Fn 1 Elemente 1 i 2 2 1 Fn 2 Fn 1 N 1 1 Fn 1 Analyse: Durchsuchen von Fn 1 Elementen in max. n Schlusselvergleichen. Nun ist p !n! p !n 1 1 + 5 1 5 Fn = p 2 2 5 n c 1:618 ; mit einer Konstanten c: Ergo: Cmax (N ) = O(log : (N + 1)) = O(log N ) 1 618 2 6.3.1 Implementation public static int search(Orderable A[],Orderable k){ /* Durchsucht A[1], .., A[n] nach Element k und liefert den Index i mit A[i] = k; -1 sonst */ int n = A.length-1; int fibMinus2 = 1, fibMinus1 = 1, fib = 2; while (fib - 1 < n) { fibMinus2 = fibMinus1; fibMinus1 = fib; fib = fibMinus1 + fibMinus2; } int offset = 0; 6.3. FIBONACCI-SUCHE while (fib > 1) { /* Durchsuche den Bereich [offset+1,offset+fib-1] nach Schluessel k (Falls fib = 2, dann besteht [offset+1,offset+fib-1] aus einem Element!) */ int m = min(offset + fibMinus2,n); if (k.less(A[m])) { // Durchsuche [offset+1,offset+fibMinus2-1] fib = fibMinus2; fibMinus1 = fibMinus1 - fibMinus2; fibMinus2 = fib - fibMinus1; } else if (k.greater(A[m])) { // Durchsuche [offset+fibMinus2+1,offset+fib-1] offset = m; fib = fibMinus1; fibMinus1 = fibMinus2; fibMinus2 = fib - fibMinus1; } else // A[m] = k return m; } return -1; } } 113 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 114 6.4 Exponentielle Suche Annahme: n sehr gro, i mit ai = k klein Denke-Zahl-Aus: Logarithmische Anzahl von Fragen Eingabe: Sortierte Folge a ; : : : ; an, Schlussel k Ausgabe: Index i mit ai = k 1 j = 1; while (k > a_j) j = 2*j; return search (a,j/2,j,k); Index j von aj 1 2 4 Analyse: - aj k: dlog ie - Binare Suche: 2dlog(i=2 + 1)e Gesamtaufwand: O(log i) 8 a8 < k i 16 a16 > k 6.5. INTERPOLATIONSSUCHE 115 6.5 Interpolationssuche Idee: Suche von Namen im Telefonbuch, z.B. Bayer und Zimmermann l al ? Suchschlussel k ar a r Erwartete Position von k (bei Gleichverteilung aller gespeicherten Schlussel): Analyse: l + (r l) ak aal r l - im schlechtesten Fall: O(n) - im Mittel bei Gleichverteilung: O(log log n) KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 116 6.6 Selbstanordnende lineare Listen MF-Regel (Move-to-front): Mache ein Element zum ersten Element der Liste, nachdem auf das Element (als Ergebnis einer erfolgreichen Suche) zugegrien wurde. T-Regel (Transpose): Vertausche ein Element mit dem unmittelbar vorangehenden, nachdem auf das Element zugegrien wurde. FC-Regel (Frequency Count): Nach jedem Zugri auf ein Element wird dessen Haugkeitszahler um 1 erhoht. Ferner wird die Liste nach jedem Zugri neu geordnet und zwar so, da die Haugkeitszahler der Elemente in absteigender Reihenfolge sind. Beispiel Liste L = f1; 2; 3; 4; 5; 6; 7g. a) Greife 10x nacheinander auf 1; : : : ; 7 zu. b) Greife 10x auf 1, dann zehnmal auf 2, usw. zu. 78 78 MTF: a) 2 = 6:7 b) 2 = 1:3 Durchschnittliche (statischen) Zugriskosten: (10 Pi i)=70 = 4 +7 9 7 10 7 +9 7 1 10 7 7 =1 6.6.1 Gute von Move-To-Front Experimente: MTF besser als T und FC (U bung) Ziel: Vergleich MTF mit beliebiger Strategie s = s s s : : : sm : Folge von Suchanfragen A : Verfahren zur Selbstanordnung P m CA(s) = P i CA(si) : Kosten zur Verarbeitung von s VA(s) = Pmi VA(si) : # Vertauschungen nach vorn HA(s) = mi HA(si) : # Vertauschungen n. hinten Bemerkung: Fur MTF-, T- und FC-Regel gilt: HMTF (s) = HT (s) = HFC (s) = 0 Weiterhin: VA(s) CA(s) m. Satz: Fur jeden Algorithmus A zur Selbstanordnung von Listen und fur jede Folge s von m Zugrisoperationen gilt CMTF (s) 2 CA(s) + HA(s) VA(s) m: D.h. (grob) MTF-Regel hochstens doppelt so schlecht ist wie jeder andere Algorithmus zur Selbstanordnung von Listen. 1 2 3 =1 =1 =1 6.6.2 Amortisierung Ziel: Durchschnittlicher Aufwand fur eine schlechtestmogliche Folge von Operationen Potentialmethode (Tarjan et al.) Bankkontoparadigma (Mehlhorn et al.) 6.6. SELBSTANORDNENDE LINEARE LISTEN 117 Idee: Zahle fur billige Operationen etwas mehr und verwende Erspartes, um fur teure Operationen zu zahlen Kosten: tl wirkliche Kosten der l-ten Operation Kontostand/Potential l nach Ausfuhrung der l-ten Operation Amortisierte Zeit: al wirkliche Schrittzahl (Zeit) tl plus die Dierenz l l der 1 Kontostande m X l=1 m X l=1 al = tl = 6.6.3 Exkurs Binarzahler l = # Einsen im Zahler m X l=1 m X l=1 tl + m ; also m X al + m al 0 0 l=1 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 118 Operation 0 Zahlerstand 0000 tl 1 0001 1 1 2 2 0010 2 1 2 3 0011 1 2 2 4 0100 3 1 3 + (1 2) = 2 5 0101 1 2 2 6 0110 2 2 2 7 0111 1 3 2 8 1000 4 1 4 + (1 3) = 2 9 1001 .. . 1 2 tl l 2 .. . al = 2 .. . l .. . m l al = tl + l l 1 0 6.6.4 Move-To-Front Analyse Kontostand: Sei LAl Liste nach l-ter Operation fur Verfahren A, bal(L ; L ) Anzahl der Inversionen in einer Liste L bzgl. L , d.h. bal(L ; L ) = jf(i; j ) j pos (i) > pos (j ) und pos (i) < pos (j )gj 1 1 1 2 2 1 1 2 2 2 l = bal(LAl ; LMF l ) a) = bal(LA ; LMF ) = 0 und b) l 0 0 Beispiel: 0 0 L : 4; 3; 5; 1; 7; 2; 6 L : 3; 6; 2; 5; 1; 4; 7 In L : 3 vor 4, 6 vor 2, 6 vor 5, 6 vor 1, 6 vor 4, 6 vor 7, 2 vor 5, 2 vor 1, 2 vor 4, 2 vor 7, 5 vor 4, 1 vor 4, aber in L der Reihe nach jeweils die umgekehrte Relation; alle anderen Paare stehen in L und L in derselben Anordnung. 1 2 2 1 2 1 6.6. SELBSTANORDNENDE LINEARE LISTEN 119 ! bal(L ; L ) = 12 = bal(L ; L ). Umbenennung: 1 2 2 1 L : 1; 2; 3; 4; 5; 6; 7 L : 2; 7; 6; 3; 4; 1; 5 1 2 6.6.5 Schlufolgerung Wissen: tAl = i und tMF l =k 1 LA : ::: 2 1 r r r r r xi r r r r r i xi k LMTF : i ::: xi : # Elemente vor i in LMTF und nach i in LA (k 1 xi) : # Elemente vor i in LMTF und LA tl +)bal(xLi A+0 ; (LkMTF10) xibal ) A(sl ): bal(LA 0 ; LMTF 0) = bala(lLA=; LMTF ) (LVAA;(LslMTF )+H Damit: = k xi + (k 1 xi) VA(sl ) + HA(sl ) = 2(k xi ) X m1 VA (sl ) + HA (sl ) ) CMTF2(is) 1 VA(sal )l + bal HA((L;sl )L: ) bal(L0 ; L00) l=1 2CA(s) + HA(s) VA(s) m 120 6.7 Suchbaume KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME Baume: Baume sind verallgemeinerte Listenstrukturen. Nachfolger: Endlich viele Kinder/Sohne eines direkten Vorgangers/Vaters, i.a. (an)geordnet (erstes, zweites, drittes Kind). Ordnung : max. Anzahl von Kindern. Darstellung: (ungerichgeter) Verbund von Knoten mit ausgezeichneter Wurzel. Knoten ohne (bzw. mit null)-Nachfolger heien Blatter, sonst innere Knoten. Rekursive Denition: Baume mit Ordnung d, Hohe h: (1) Der aus einem einzigen Knoten bestehende Baum ist ein Baum der Ordnung d. Die Hohe h ist 0. (2) Sind t1; : : : ; td beliebige Baume der Ordnung d, so erhalt man einen (weiteren) Baum der Ordnung d, indem man die Wurzeln von t1 ; : : : ; td zu Sohnen einer neugeschaenen Wurzel w macht. Die Hohe h ist maxfh(t1); : : : ; h(td )g + 1 w b b b A 1A A t b b p p p p p p p p bp A A t A 2 A b t A d A Festlegung: d = 2 Binarbaume, d > 2 Vielwegbaume. 6.7.1 Suchbaume Annahme: Total geornete Menge von Schlusseln. Charakterisierung: Die Schlussel im linken Teilbaum von p sind samtlich kleiner als der Schlussel von p, und dieser ist wiederum kleiner als samtliche Schlussel im rechten Teilbaum von p. Implementation: (Schlussel ist Ganzzahl) public class Knoten { Knoten leftson, rightson; int key; // Hier koennte noch ein Infotype kommen Knoten (int key) { this.key = key; } public String toString() { StringBuffer st = new StringBuffer(""+key); return st.toString(); } } 6.7. SUCHBAUME Worterbuch 121 - Dictionary: Operationen Suchen/Einfugen/Entfernen search/insert/delete public class Baum { Knoten Wurzel; Baum() { Wurzel = null; } public Knoten Suchen(int k) ... public void Einfuegen(int k) ... public void Entfernen(int k) ... public String toString() ... } 6.7.2 Beispiel Ohne Stopper: Wurzel PP PP q q14 q Mit Stopper: HH HH HH HH H j H 3 q qPPP q1 q q27 qHH - q 15 q q39 q - KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 122 Wurzel 27 q qHH - q1 q HH H HH HH j H q 3 Pq PP HH PP PP q q39 q q 15 q q14 q 6.7.3 Einfugen/Suchen Top-Level Aufrufe: public Knoten Suchen(int k) { return Suchen(Wurzel,k); } public void Einfuegen(int k) { Wurzel = Einfuegen(Wurzel,k); } Rekursive Aufrufe: Knoten Suchen(Knoten p, int x) { if (p == null) return null; if (x < p.key) return Suchen(p.leftson,x); else if (x > p.key) return Suchen(p.rightson,x); return p; } Knoten Einfuegen(Knoten p, int k) { if (p == null) return new Knoten(k); - ? ? - qx q 6.7. SUCHBAUME 123 else if (k < p.key) p.leftson = Einfuegen(p.leftson,k); else if (k > p.key) p.rightson = Einfuegen(p.rightson,k); else return null; // Schluessel kam vor return p; } 6.7.4 Sonderfalle Entartung: q 1 qA A AU q 3 qA A AU q 14 qA A AU q 15 qA A AU 27 q qA A Loschen eines Knotens: AU KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME 124 q x q@ p y q q@ p @ @ @ @ @ @ R @ q q pp p @ R @ q q =) y q qA 6.7.5 Loschen AAU q public void Entfernen(int k) { Wurzel = Entfernen(Wurzel,k); } Knoten vatersymnach (Knoten p) { if (p.rightson.leftson != null) { p = p.rightson; while (p.leftson.leftson != null) p = p.leftson; } return p; } Knoten Entfernen(Knoten p, int k) { if (p == null) return p; if (k < p.key) p.leftson = Entfernen(p.leftson,k); else if (k > p.key) p.rightson = Entfernen(p.rightson,k); else { @ q pp p y q qA A AU 6.7. SUCHBAUME 125 if (p.leftson == null) return p.rightson; if (p.rightson == null) return p.leftson; Knoten q = vatersymnach(p); if (q == p) { p.key = p.rightson.key; q.rightson = q.rightson.rightson; } else { p.key = p.leftson.key; q.leftson = q.leftson.rightson; } } return p; } 6.7.6 Durchlaufreihenfolgen Voraussetzung: Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel r und Kindern v ; : : : ; vn. Denition: 1 Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v )) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v )) r in(T (v )) : : : in(T (vk )) und der Preorderdurchlauf pre(T ) als r pre(T (v )) : : : pre(T (vk )) festgelegt. 1 1 2 1 1. Preorder und Inorder beschreiben einen Baum T nicht eindeutig. Gegenbeispiel: pre(T ) = pre(T ) = 1; 2; 3; 4; 5; 6 in(T ) = in(T ) = 2; 1; 4; 3; 5; 6 Einen binaren Baum ubrigens schon (U bung) 2. Preorder und Postorder beschreiben T eindeutig (U bung). 1 1 2 2 6.7.7 Implementation String Inorder(Knoten p) { // Symm. Reihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer(Inorder(p.leftson) + "(" + p.toString() + ")" + Inorder(p.rightson)); 126 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME return st.toString(); } String Preorder(Knoten p) { // Hauptreihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer("(" + p.toString() + ")" + Preorder(p.leftson) + Preorder(p.rightson)); return st.toString(); } String Postorder(Knoten p) { // Nebenreihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer(Postorder(p.leftson) + Postorder(p.rightson) + "(" + p.toString() + ")"); return st.toString(); } public String toString() { StringBuffer st = new StringBuffer( "Preorder : " + Preorder(Wurzel) + "\n" + "Inorder : " + Inorder(Wurzel) + "\n" + "Postorder: " + Postorder(Wurzel)); return st.toString(); } 6.7.8 Interne Pfadlange (0): Ist t ein Blatt, so ist I (t) = 0. (1): Ist t ein Baum mit linkem Teilbaum mit Wurzel tl und rechtem Teilbaum mit Wurzel tr , so ist X I (t) := I (Itl()t+ inneren ) =I (tr ) + Zahl der(Tiefe( p) Knoten + 1) von t: p p innerer Knoten von t EI (n) : Erwartungswert fur die interne Pfadlange eines zufallig erzeugten binaren Suchbaumes mit n inneren Knoten, so gilt EI (0) = 0; EI (1) = 1; n X EI (n) = n1 (EI (k 1) + EI (n k) + n) k =1 6.7. SUCHBAUME 127 n X n X =1 =1 = n + 1 ( EI (k 1) + EI (n k)) nk k Satz: EI (n) 1:386n log n 0:846n + O(log n): Beweis: QUICKSORT Average-Case (+2N ) Beweis: 2 I) QUICKSORT Average-Case (+2N ) II) Direkt: N X EI (N + 1) = (N + 1) + N 2+ 1 EI (k); k=0 und daher (N + 1) EI (N + 1) = (N + 1) + 2 2 N EI (N ) = N + 2 2 NX1 k=0 N X k=0 EI (k) EI (k): Aus den beiden letzten Gleichungen folgt (N + 1)EI (N + 1) N EI (N ) = 2N + 1 + 2 EI (N ) (N + 1)EI (N + 1) = (N + 2)EI (N ) + 2N + 1 N + 2 EI (N ): EI (N + 1) = 2NN ++11 + N +1 Nun zeigt man leicht durch vollstandige Induktion uber N , da fur alle N 1 gilt: EI (N ) = 2(N + 1)HN 3N Dabei bezeichnet HN = 1+ +: : :+ N die N -te harmonische Zahl, die wie folgt abgeschatzt werden kann: HN = ln N + + 21N + O( N1 ) 1 2 1 2 Dabei ist = 0:5772 : : : die sogenannte Eulersche Konstante. Damit ist EI (N ) = 2N ln N (3 2 ) N + 2 ln N + 1 + 2 + O( N1 ) 128 KAPITEL 6. SUCHVERFAHREN UND SUCHBAUME und daher EI (N ) = 2 ln N (3 2 ) + 2 ln N + : : : N N 2 = log N (3 2 ) + 2 lnN + : : : log e N 2 log 2 = log e log N (3 2 ) + 2 lnNN + : : : 1:386 log N (3 2 ) + 2 lnNN + : : : 2 2 10 2 10 2 Kapitel 7 Balancierte Baume 129 130 7.1 Durchlaufreihenfolgen KAPITEL 7. BALANCIERTE BAUME Voraussetzung: Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel r und Kindern v ; : : : ; vk (dabei ist k = 0 moglich). Denition: 1 Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v )) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v )) r in(T (v )) : : : in(T (vk )), der Preorderdurchlauf pre(T ) als r pre(T (v )) : : : pre(T (vk )) und der Levelorderdurchlauf als lev(T ) als r lev(T (v )) r lev(T (v )) : : : r lev(T (vk )) r festgelegt. 1 1 2 1 1 2 7.1.1 Eigenschaften Beispiel: Preorder: D,E,B,H,K,L,I,F,G,C,A Postorder: A,B,D,E,C,F,H,I,K,L,G Inorder: D,B,E,A,H,F,K,I,L,C,G Levelorder: A,B,D,B,E,B,A,C,F,H,F,I,K,I,L,I,C,G,C,A 1. Pre- In-, und Postorder haben jeweils die Lange n. 2. Pre- In-, und Postorder allein nicht eindeutig. 3. Levelorder hat die Lange 2n 1 4. Levelorder ist eindeutig. (Induktion: jlev(T )j = 1 ist klar. Ansonsten ist der erste Knoten Wurzel. Zwischen den verschiedenen Darstellungen der Wurzel nden wir die Levelorder der Teilbaume.) 5. Preorder und Inorder beschreiben einen Baum T nicht eindeutig. Gegenbeispiel: pre(T ) = pre(T ) = 1; 2; 3; 4; 5; 6 in(T ) = in(T ) = 2; 1; 4; 3; 5; 6 Einen binaren Baum ubrigens schon (U bung) 6. Preorder und Postorder beschreiben T eindeutig (U bung). 1 1 2 2 7.1.2 Sortieren mit naturlichen Suchbaumen Idee: Baue fur die Eingabefolge einen naturlichen Suchbaum auf und gebe die Schlussel in symmetrischer Reihenfolge (Inorder) aus. 7.1. DURCHLAUFREIHENFOLGEN 131 Bemerkung: Abhangig von der Eingabereihenfolge kann der Suchbaum degenerieren. Komplexitat: Abhangig von der internen Pfadlange Schlechtester Fall: Vorsortierung ) (n ) Schritte. Bester Fall: Ausgeglichener Suchbaum (Analyse ahnlich wie die des mittleren Falles von binarer Suche) ) O(n log n) | mit Faktor 1 vor n log n Mittlerer Fall: Erwartungswert fur interne Pfadlange ist 1:386n log n 0:846n + 2 2 O(log n). P.S. Beliebte Prufungsfrage 7.1.3 Interne Pfadlange (0): Ist t ein Blatt, so ist I (t) = 0. (1): Ist t ein Baum mit linkem Teilbaum mit Wurzel tl und rechtem Teilbaum mit Wurzel tr , so ist X I (t) := I (Itl()t+ inneren p) Knoten + 1) von t: ) =I (tr ) + Zahl der(Tiefe( p p innerer Knoten von t EI (n) : Erwartungswert fur die interne Pfadlange eines zufallig erzeugten binaren Suchbaumes mit n inneren Knoten, so gilt EI (0) = 0; EI (1) = 1; n X EI (n) = n1 (EI (k 1) + EI (n k) + n) k n n X X = n + n1 ( EI (k 1) + EI (n k)) k k =1 =1 Satz: EI (n) 1:386n log n 0:846n + O(log n): Beweis: QUICKSORT Average-Case (+2N ) 2 7.1.4 Implementation String Inorder(Knoten p) { // Symm. Reihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer(Inorder(p.leftson) + "(" + p.toString() + ")" + Inorder(p.rightson)); return st.toString(); } =1 132 KAPITEL 7. BALANCIERTE BAUME String Preorder(Knoten p) { // Hauptreihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer("(" + p.toString() + ")" + Preorder(p.leftson) + Preorder(p.rightson)); return st.toString(); } String Postorder(Knoten p) { // Nebenreihenfolge if (p == null) return new String(""); StringBuffer st = new StringBuffer(Postorder(p.leftson) + Postorder(p.rightson) + "(" + p.toString() + ")"); return st.toString(); } public String toString() { StringBuffer st = new StringBuffer( "Preorder : " + Preorder(Wurzel) + "\n" + "Inorder : " + Inorder(Wurzel) + "\n" + "Postorder: " + Postorder(Wurzel)); return st.toString(); } 7.2. AVL-BAUME 133 7.2 AVL-Baume Namensgebung: Schopfer Adelson-Velskii und Landis (1962) Denition: Ein binarer Suchbaum heit AVL-Baum, wenn fur jeden Knoten gilt, da sich die Tiefe des rechten Teilbaumes d(Tr ) und die Tiefe des linken Teilbaumes d(Tl) um maximal 1 unterscheiden. Balancegrad: b(v) = d(Tl) d(Tr ) 2 f 1; 0; 1g Satz: Die Tiefepeines AVL-Baumes p mit n Daten betragt mindestens dlog(n + 1)e 1 und hochstens log( 5(n + 2))= log(( 5 + 1)=2) 1:44 log n: Beweis: Die untere Schranke gilt sogar fur alle binaren Suchbaume. z.Z. minimale Anzahl der Knoten in AVL-Baum A(d) der Tiefe d ist F (d + 2) 1, denn F (dp+ 2) 1 = A(d) pn ) d+3 log( 5(n + 2))= log(( 5 + 1)=2) 1:44 log n Nun ist gema der Balancebedingung A(d) = 1 + A(d 1) + A(d 2) = 1 + F (d + 1) 1 + F (d) 1 = F (d + 2) 1 7.2.1 Einfugen O.b.d.A. Suchpfad ruckwarts vom rechten Sohn. Die Tiefe des rechten Teilbaumes sei gewachsen. Fall 1. Vorher b(v) = 1. Setze b(v) = 0. Fall 2. Vorher b(v) = 0. Setze b(v) = 1. (Rebalancierung am Vater fortsetzen.) Fall 3. Vorher b(v) = 1 (aktuell gilt b(v) = 2). Sei x rechter Sohn von v und w der linke Sohn von x. Fall 3a. Rechter Teilbaum von x gewachsen: Linksrotation an v. Setze b(v) = 0; b(x) = 0 Fall 3b. Rechter Teilbaum von w gewachsen: Rechtsrotation an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 1; b(x) = 0 Fall 3c. Linke Teilbaum von w gewachsen: Rechtsrotation an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 0; b(x) = 1 Fall 3d. w hat keinen Sohn. Rechtsrotation an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 0; b(x) = 1 Rebalancierung nach einer Rotation oder Doppelrotation abgeschlossen. 7.2.2 Entfernen O.b.d.A. Datum in linken Teilbaum von v geloscht. Fall 1. Vorher b(v) = 1. Setze b(v) = 0. (Rebalancierung am Vater fortsetzen.) Fall 2. Vorher b(v) = 0. Setze b(v) = 1. 134 KAPITEL 7. BALANCIERTE BAUME Fall 3. Vorher b(v) = 1 (aktuell gilt b(v) = 2). Sei x rechter Sohn von v und w der linke Sohn von x. Fall 3a. Beide Teilbaum von x haben gleiche Tiefe: Linksrotation an v. Setze b(v) = 1; b(x) = 1 Fall 3b. Rechte Teilbaum von x um 1 tiefer als der linke Teilbaum: Linksrotation an v. Setze b(v) = 0; b(x) = 0: (Rebalancierung am Vater fortsetzen.) Fall 3c. b(x) = 1; b(w) = 1: Rechtsrotation an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 0; b(x) = 1. (Rebalancierung am Vater fortsetzen.) Fall 3d. b(x) = 1; b(w) = 1: Rechtsrotation an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 0; b(x) = 0. (Rebalancierung am Vater fortsetzen.) Fall 3e. b(x) = 1; b(w) = 1. Rechtsrotation. (Rebalancierung am Vater fortsetzen.) an x, dann Linksrotation an v. Setze b(w) = 0; b(v) = 1; b(x) = 0. (Rebalancierung am Vater fortsetzen.) 7.2.3 Implementation Knotenklasse: class AVLNode { int content; // Inhalt, hier integer byte balance; // fuer Werte -2,-1,0,1,+2 AVLNode left; // linker Nachfolger AVLNode right; // rechter Nachfolger AVLNode (int c){ // Konstruktor fuer Knoten content = c; // uebergebener Inhalt balance = 0; // Balance ausgeglichen left = right = null; }// erst keine Nachfolger } Baumklasse: class AVLTree { AVLNode root; // Die Wurzel des Baumes boolean grown, shrunk, found; // Hilfsvariablen AVLTree () { root = null; grown = shrunk = found = false; } boolean search (int c) ... boolean insert (int c) { 7.2. AVL-BAUME found = false; grown = true; root = insert (root, c); return !found; } AVLNode insert (AVLNode n, int c) ... void rotateRight (AVLNode n) ... void rotateLeft (AVLNode n) ... boolean delete (int c) { found = shrunk = true; root = delete(root, c); return found; } AVLNode delete (AVLNode n, int c) ... } 7.2.4 Rotationen Einfache Rotation nach rechts: void rotateRight (AVLNode n) { AVLNode m = n.left; int cc = n.content; n.content = m.content; m.content = cc; n.left = m.left; m.left = m.right; m.right = n.right; n.right = m; int bm = 1+Math.max(-m.balance,0)+n.balance; int bn = 1+m.balance + Math.max(0,bm); n.balance = (byte)bn; m.balance = (byte)bm; } Einfache Rotation nach links: void rotateLeft (AVLNode n) { AVLNode m = n.right; int cc = n.content; n.content = m.content; m.content = cc; n.right = m.right; m.right = m.left; 135 KAPITEL 7. BALANCIERTE BAUME 136 m.left = n.left; n.left = m; int bm = -(1+Math.max(+m.balance,0)-n.balance); int bn = -(1-m.balance + Math.max(0,-bm)); n.balance = (byte)bn; m.balance = (byte)bm; } 7.2.5 Einfugen Fuge c in Teilbaum ein: AVLNode insert (AVLNode n, int c){ if (n == null) return new AVLNode (c); if (c == n.content) { found = true; grown = false; } else { if (c < n.content) { n.left = insert (n.left, c); if (grown) n.balance--; } else { n.right = insert (n.right, c); if (grown) n.balance++; } switch (n.balance) { case -2: if (n.left.balance == +1) rotateLeft (n.left); // D-Rot rotateRight (n); grown = false; break; case -1: break; case 0: grown = false; break; case +1: break; case +2: if (n.right.balance == -1) rotateRight (n.right); // D-Rot rotateLeft (n); grown = false; break; default: balanceError (n); break; } } return n; } 7.2. AVL-BAUME 7.2.6 Loschen AVLNode delete (AVLNode n, int c){ if (n == null) { found = shrunk = false; return n; } if (c == n.content) { if (n.left == null) return n.right; if (n.right == null) return n.left; n.content = c = minValue (n.right); } if (c < n.content) { n.left = delete (n.left, c); if (shrunk) n.balance++; } else { n.right = delete (n.right, c); if (shrunk) n.balance--; } switch (n.balance) { case -2: switch (n.left.balance) { case +1: rotateLeft(n.left); break; case 0: shrunk = false; break; case -1: break; default: balanceError (n.left); break; } rotateRight (n); break; case -1: shrunk = false; break; case 0: break; case +1: shrunk = false; break; case +2: switch (n.right.balance) { case -1: rotateRight(n.right); break; case 0: shrunk = false; break; case +1: break; default: balanceError (n.right); break; } rotateLeft (n); break; default: balanceError (n); break; } return n; } 137 138 7.3 Bayer-Baume KAPITEL 7. BALANCIERTE BAUME Denition: Ein Baum heit B -Baum der Ordnung m, wenn die folgenden Eigenschaften erfullt sind. 1. Jeder Knoten mit Ausnahme der Wurzel enthalt mindestens dm=2e 1 Daten. Jeder Knoten enthalt hochstens m 1 Daten. Die Daten sind sortiert. 2. Knoten mit k Daten x ; : : : ; xk haben k + 1 Zeiger, die auf die Bereiche (; x ); (x ; x ); : : : ; (xk ; xk ); (xk ; ) zeigen. 3. Die Zeiger, die einen Knoten verlassen, sind entweder alle null-Zeiger (Blatter) oder alle echte Zeiger. 4. Alle Blatter haben gleiche Tiefe. Nutzung: Externe Speicherung (m Seitengroe) Bemerkung: 2-3 Baume sind B -Baume der Ordnung 3. Speicherauslatung: Fur alle Knoten mindestens 50 Prozent. 1 1 1 2 1 7.3.1 Tiefe Satz: Tiefe eines B -Baumes der Ordnung m mit n Daten liegt im Intervall [dlogm(n + 1) 1e; dlogdm= e ((n + 1)=2)e] Beweis: Analog zu Satz: Die Tiefe von 2-3-Baumen mit n Daten betragt mindestens dlog (n + 1)e 1 und hochstens dlog(n + 1)e 1 Beweis: Maximale Datenzahl in Tiefe d is 2(1 + 3 + : : : + 3d) = 3d 1 (vollstandiger 2 3 +1 ternarer Baum) Minimale Datenzahl in Tiefe d is 1 + 2 + : : : + 2d = 2d Fur die Tiefe d eines 2-3 Baumes mit n Daten gilt: 3d 1 n und d dlog (n + 1)e 1 und 2d 1 n und d dlog(n + 1)e 1 +1 +1 1 (vollstandiger binarer Baum) 3 +1 7.3.2 Einfugen Suche von x In Knoten v wird x mit binarer Suche in logm Vergleichen eingefugt mit log m + logm n log n Nach erfolgloser Suche Einfugen von x Gefundener null-Zeiger wird um x erweitert. Allgemein ist Zeiger auf Knoten mit Datum y gegeben, so da die Blatter des zugehorigen Teilbaumes eine Ebene zu tief liegen. Enthalt der Knoten (inclusive m) 7.3. BAYER-BAUME 139 - weniger als m 1 Daten, dann wird y eingefugt. - Genau m Daten z < : : : < zm , d.h. Knoten ist voll. Bilde 3 Knoten mit den folgenden Daten: z ; : : : ; zdm= e (links, dm=2e -1 Daten) zdm= e ; : : : ; zm (rechts, dm=2e -1 Daten) y (Vater, 1 Datum) und verteile die Nachfolgezeiger des ursprunglichen Knotens Rekursion: Gehe Suchpfad einen Schritt zuruck und fahre analog fort 1 1 2 1 2 +1 7.3.3 Loschen Nach erfolgreicher Suche: 1. Wenn x nicht in einem Blatt abgelegt ist, wird x mit dem groten Datum y < x vertauscht. Dieses Datum ist mit Sicherheit in einem Blatt 2. Entferne x und null-Zeiger. Enthalt der Knoten (exclusive m) - mindestens dm=2e Daten, dann wird x geloscht - ein Bruder mindestens dm=2e Daten, dann genugt eine einfache Datenrotation - alle Bruder dm=2e Daten, dann wird kontraktiert. Betrachte o.b.d.A. linken Bruder und x0 als trennendes Datum im Vater. Dieses x0 wird als Knoten extrahiert. Nun wird aus den 3 Knoten ein Knoten mit 2dm=2e 2 m 1 Daten und dm=2e + dm=2e 1 Nachfolgezeiger Rekursion: Gehe Suchpfad einen Schritt zuruck und fahre analog fort 7.3.4 Anhangen (2-3 Baum) Ziel: Konstruiere aus den 2-3 Baumen T und T , wobei alle Daten in T kleiner als alle 1 2 1 Daten in T sind, den 2-3-Baum T , der die Daten aus T und T enthalt. Annahme: Schlussel in Blattern, innere Knoten entalten da grote Datum des linken Teilbaumes und (falls vorhanden) des mittleren Teilbaumes. 1. Fall: d(T ) = d(T ). T besteht nur aus einer Wurzel, die als neuer Knoten da groere Datum z aus T und als Teilbaume T (ohne dieses Datum) und T enthalt. 2. Fall: O.B.d.A. d(T ) > d(T ). Bilde T 0 mit Zeiger auf die Wurzel von T . Gehe von der Wurzel in T d(T ) d(T ) 1 mal zum rechten Kind und ende an v. 2a. v hat enthalt ein Datum x: Schreibe x mit den 2 Teilbaumen in die Wurzel T 0, zusatzlich kommt da groere Datum z aus T in die Wurzel. 2 1 1 2 1 1 1 1 2 1 2 2 2 2 1 KAPITEL 7. BALANCIERTE BAUME 140 2b. v hat enthalt zwei Daten x und y: Bilde weiteren Wurzelknoten mit y an der Wurzel, x zur linken und z als Wurzel von T 0 zur rechten. Reihe die 4 Teilbaume entsprechend ein. Satz: Anhangen (engl. Concatenate) kann fur 2-3 Baume in Zeit O(jd(T ) d(T )j + 1) durchgefuhrt werden. 1 2 7.3.5 Aufteilen (2-3 Baum) Ziel: Konstruiere aus den 2-3 Baumen T , der a enthalt, zwei 2-3 Baume, wobei T alle Daten x a und T alle Daten x > a enthalt. Suche: Auf dem Weg zum Blatt mit Schlussel a werden die folgenden Informationen 1 2 gespeichert: Der Suchpfad und alle Kinder von Knoten auf dem Suchpfad. Aufbau von T : (analog T ) Seien T ; : : : ; T m die Teilbaume, aus denen wir T zusammensetzen. Es gilt: d(T i) d(T i ). Algorithmus: Fur alle i = m 1; : : : ; 1 entsteht T i aus dem Anhangen von T i an T i. Schlielich ist T = T Beobachtung: Das Anhangen aller T i mit d(T i) d0 ergibt einen 2-3 Baum der Tiefe d0 + 1. (Beweis durch Induktion uber d0) Analyse: Insgesamt hangen m 1 2d(T ) 1 mal an. Es seien d < : : : < dk so gewahlt, j da mindestens einer anzuhangenden Baume T Tiefe di hat, 1 i k. Die Konkatenateion mit den ein oder zwei Baumen der Tiefe di verursacht kosten von O(di di + 1) also insgesamt O(dk d + d(T )) = O(d(T )) Satz: Aufteilen (engl. Split) kann fur 2-3 Baume in Zeit O(d(T )) durchgefuhrt werden. 1 2 1 1 1 +1 1 1 1 1 1 1 1 1 1 +1 1 1 1 +1 1 +1 1 7.4. BRUDER-BAUME 141 7.4 Bruder-Baume Idee: Bruder-Baume kann man in einem prazisierbaren Sinn als expandierte AVL-Baume auffassen. Unarer Knoten Durch Einfugen unarer Knoten an den richtigen Stellen erhalt man einen Baum, dessen samtliche Blatter dieselbe Tiefe haben; und umgekehrt entsteht aus einem Bruder-Baum ein hohenbalancierter Baum, wenn man die unaren Knoten mit ihren einzigen Sohnen verschmilzt. Denition: Ein binarer Baum heit ein Bruder-Baum, wenn jeder innere Knoten einen oder zwei Sohne hat, jeder unare Knoten einen binaren Bruder hat und alle Blatter dieselbe Tiefe haben. m m , , m L \ \ l l m L m m m m L m m L \ \ \ \ L Bruder{Baum m m m m L kein Bruder-Baum m mQ Q Q m m m m @@ m m TT m m m TT m L L L kein Bruder-Baum L L Bruder-Baum 7.4.1 Hohe Ein Bruder-Baum mit Hohe h hat wenigstens Fh Blatter. (Fi ist die i-te FibonacciZahl.) Also umgekehrt: Ein Bruder-Baum mit N Blattern und (N 1) inneren Knoten hat eine Hohe h 1:44 : : : log N . +2 2 KAPITEL 7. BALANCIERTE BAUME 142 Hohe Bruder-Baume mit Blattzahl minimaler Blattzahl 1 m 2 m 3 L 2 m m TT L m 3 5 m m m m TT m @ @ L L ... h+2 h6 ? m m T T DD D D D D D D D ... 6 h + 1 Fh +4 ? Einfugen Fall 1: p hat nur einen Sohn p Fall 2: p hat bereits zwei Sohne m=) xm L fertig! 7.4. BRUDER-BAUME 143 p :m L up(p; m; x) Invariante: Wenn up(p; m; x) aufgerufen wird, gilt: (1) p hat zwei Sohne pl und pr , die beide Wurzeln von 1-2-Bruder-Baumen sind. (2) Der Knoten m ist entweder ein Blatt oder hat einen einzigen Sohn, der Wurzel eines 1-2-Bruder-Baumes ist. (3) Schlussel im linken Teilbaum von p < x < Schlussel im Teilbaum von m < Schlussel von p < Schlussel im rechten Teilbaum von p a. [p hat einen linken Bruder mit zwei Sohnen] b. [p hat einen rechten Bruder mit zwei Sohnen] c. [p hat einen linken Bruder mit einem Sohn] d. [p hat einen rechten Bruder mit einem Sohn] e. [p hat keinen Bruder] 7.4.2 Eigenschaften Falle: (1) binar auf unar, (3) 2 binar auf binar, (2) sonst. Statisch: Fur jeden unaren Knoten auf Niveau l mu es einen binaren Bruder auf demselben Niveau geben. Daher gilt fur das Verhaltnis Anzahl binare Knoten auf Niveau l und l + 1 : U = Anzahl Knoten insgesamt auf Niveau l und l + 1 Konguration U (2) (3) (1) und eine Kong. aus (2) (1) und (3) 2 3 3 3 3 5 4 5 Somit U 1 und 3=5 der inneren Knoten binar Da die amortisierte Anzahl von Aufrufen von up pro Einfugung konstant ist, gilt: Folgerung: Der mittlere Aufwand zum Einfugen ist konstant Bemerkung: Entsprechende Aussage fur AVL-Baume wesentlich schwieriger zu zeigen. 3 5 KAPITEL 7. BALANCIERTE BAUME 144 7.4.3 Implementation Bruder-Baume Grundstruktur: class Btree{ Bnode root; private final boolean UNARY = true; private final boolean BINARY = false; private String s; Btree(){ root=new Bnode(0,null,null,UNARY); } Bnode search(int key) ... Bnode getFather(int key) ... void up(Bnode p, Bnode m ,int x) ... void insert(int key) ... public String toString() .. } GetFather: Liefert den Vater des Knotens p mit p.key==key bzw. null, falls p==root. Existiert kein Knoten p mit p.key==key, so wird der Knoten mit nachst groerem Schlussel zuruck geliefert. Bnode getFather(int key) { Bnode dummy = root; Bnode d = null; if (dummy.isunary) return null; while (dummy!=null && dummy.key!=key){ if (dummy.isunary) dummy = dummy.leftSon; d = dummy; if(dummy.key < key) dummy = dummy.rightSon; else dummy = dummy.leftSon; } return d; } 7.4.4 Suche und Einfugen in Bruder-Baumen Bnode search(int key) { Bnode dummy = getFather(key); if (dummy==null) return root; 7.4. BRUDER-BAUME if (dummy.leftSon==null) return null; dummy = (dummy.key<key) ? dummy.rightSon : dummy.leftSon; return (dummy.key==key) ? dummy : null; } Insert fugt den Schlussel key in den Baum ein void insert(int key) { Bnode p=getFather(key); if (p==null) { root.key=key; root.isunary=false; } else up(p,null,key); } Up void up(Bnode p, Bnode m ,int x) { Bnode father=(p!=null) ? getFather(p.key) : null; if (p.key < x){ int temp = p.key; p.key = x; x = temp; } } ... } 7.4.5 Falle in Up 1. Fall p hat keinen Bruder if (p==root || father.isunary) { root = new Bnode(x, new Bnode(0,(p.leftSon==null) ? null : p.leftSon, null,UNARY), (p.rightSon==null) ? new Bnode(p.key,null,null,BINARY) : new Bnode(p.key,m,p.rightSon,BINARY), BINARY); } 145 146 KAPITEL 7. BALANCIERTE BAUME 2. Fall p hat einen linken Bruder mit nur einem Sohn else if (father.leftSon!=null && (father.leftSon).isunary) { father.leftSon = new Bnode(father.key, (father.leftSon).leftSon, p.leftSon,BINARY); father.key=x; (father.rightSon).leftSon=m; } 3. Fall p hat einen rechten Bruder mit nur einem Sohn else if (father.rightSon!=null && (father.rightSon).isunary) { father.rightSon = new Bnode(father.key, (father.rightSon).rightSon, p.rightSon,BINARY); father.key=x; (father.leftSon).rightSon=m; } 7.4.6 Weitere Falle in Up 4. Fall p hat einen linken Bruder mit zwei Sohnen else if(father.leftSon!=null && !(father.leftSon).isunary) { (father.rightSon).leftSon=m; int temp=father.key; father.key=x; up (father, new Bnode(0, (father.leftSon).leftSon, null,UNARY), temp); } 7.4. BRUDER-BAUME 5. Fall p hat einen rechten Bruder mit zwei Sohnen else if (father.rightSon!=null && !(father.rightSon).isunary) { (father.leftSon).rightSon=m; int temp=father.key; father.key=x; up (father, new Bnode(0, (father.rightSon).rightSon, null,UNARY), temp); } 147 148 7.5 Rot-Schwarz-Baume KAPITEL 7. BALANCIERTE BAUME (Knotengefarbte) Rotschwarz-Baume sind binare Suchbaume mit den folgenden Eigenschaften: i) Jeder Knoten ist entweder rot oder schwarz. ii) Jedes Blatt ist schwarz. iii) Falls ein Knoten rot ist, dann sind beide Kinder schwarz. iv) Jeder Pfad von einem Knoten zu einem Blatt enthalt die gleiche Anzahl von schwarzen Knoten. Satz: Ein (knotengefarbter) Rotschwarzbaum mit n inneren Knoten besitzt eine Hohe von hochstens 2 log(n + 1). Rotation: Entsprechend den AVL-Baumen gibt es Operationen Left-Rotate und Right-Rotate. Einfugen: Wir starten mit dem eingefugten Knoten und der Farbung \rot" und hangeln uns je nach Lage und Farbe der Ahnen mittels Rotationen und Umfarbungen uber die Vorgangerverweise an den Knoten zu den Eltern und Ureltern hinauf. Kapitel 8 Hashverfahren 149 KAPITEL 8. HASHVERFAHREN 150 8.1 Geschlossene Hashverfahren 8.1.1 Hashverfahren Worterbuchproblem: Suchen, Einfugen, Entfernen von Datensatzen (Schlusseln) Ort des Datensatzes d: Berechnung aus dem Schlussel s von d ) keine Vergleiche ) konstante Zeit Datenstruktur: lineares Feld (Array) der Groe m Hashtabelle Schlüssel s 0 1 2 i m-2 m-1 Bemerkung: Aufteilung der Menge aller moglichen Schlussel anstelle der konkret vorhandenen Schlusselmenge 8.1.2 Hashverfahren|Beispiele Beispiele: Compiler int 0x87C50FA4 int 0x87C50FA8 double 0x87C50FAC String 0x87C50FB2 i j x name ... Umgebungsvariablen (Schlussel,Attribut) Liste EDITOR=emacs GROUP=mitarbeiter HOST=vulcano HOSTTYPE=sun4 LPDEST=hp5 8.1. GESCHLOSSENE HASHVERFAHREN 151 MACHTYPE=sparc ... ausfuhrbare Programme PATH ~/bin:/usr/local/gnu/bin: /usr/local/bin:/usr/bin:/bin: : : : 8.1.3 Probleme 1. Groe der Hashtabelle Nur eine kleine Teilmenge S aller moglichen Schlussel (des Universums) U kommen vor 2. Berechnung der Adresse eines Datensatzes Schlussel sind keine ganzen Zahlen Index hangt von der Groe der Hashtabelle ab In Java: public class Object { ... public int hashCode() { ...} ... Das Universum U sollte moglichst gleichmaig auf die Zahlen 231; : : : ; 231 1 verteilt werden KAPITEL 8. HASHVERFAHREN 152 8.1.4 Hashfunktion Schlusselmenge S Hashcode Hashfunktion h fur Universum U aller 0; : : : ; m moglichen SchlusHashtabelle T sel (H (U ) [ 231; 231 1] N0) h(s) = Hashadresse h(s) = h(s0) () s und s sind Synonyme bzgl. h Adrekollision 8.1.5 Hashverfahren Beispiel fur U : alle Namen in Java mit Lange 40 ) jUj = 62 Falls jUj > m: Adrekollisionen unvermeidlich 40 Hashverfahren: 1. Wahl einer moglichst \guten" Hash-Funktion 2. Strategie zur Auosung von Adrekollisionen 1 8.1. GESCHLOSSENE HASHVERFAHREN Belegungsfaktor = # gespeicherte Schlussel = Groe der Hash-Tabelle 153 jS j = n m m Annahme: Tabellengroe m ist fest 8.1.6 Implementation in Java Ein Eintrag class TableEntry { private Object key; private Object value; ... } Die Hashtabelle abstract class HashTable { private TableEntry[] tableEntry; private int capacity; HashTable (int capacity) { this.capacity = capacity; tableEntry = new TableEntry [capacity]; for (int i = 0; i <= capacity-1; i++) tableEntry[i] = null; } protected abstract int h(Object key); public abstract void insert(Object key,Object val); public abstract void delete(Object key); public abstract Object search(Object key); } 8.1.7 Wahl der Hash-Funktion leichte und schnelle Berechenbarkeit gleichmaige Verteilung der Daten (Beispiel: Compiler) KAPITEL 8. HASHVERFAHREN 154 Divisions-Rest-Methode Wahl von m? Beispiele: h(k) = k mod m a) m gerade, letztes Bit druckt Sachverhalt aus (z.B. 0 = weiblich, 1 = mannlich) ) h(k) gerade , k gerade p b) m = 2 ) h(k) liefert p niedrigsten Dualziern von k Regel: Wahle m prim mit m 6= ri j , 0 j r 1, r = Radix der Darstellung 8.1.8 Multiplikative Methode Wahle Konstante , 0 < < 1 1. Berechne k mod 1 = k bkc 2. h(k) = bm(k mod 1)c Wahl von m unkritisch, wahle m = 2p: Berechnung von h(k): r1 0, , k r0 p Bits = h(k) Beispiel p5: 1 = 2 0:6180339 k = 123456 m = 10000 h(k) = b10000(123456 0:61803 : : : mod 1)c = b10000(76300; 0041151 : : : mod 1)c = b41:151 : : :c = 41 8.1. GESCHLOSSENE HASHVERFAHREN 155 8.1.9 Universelles Hashing Problem: h fest gewahlt ) es gibt S U mit vielen Kollisionen Idee des universellen Hashing: Wahle Hashfunktion h zufallig H endliche Menge von Hashfunktionen h 2 H : U ! f0; : : : ; m 1g Denition: H heit universell, wenn fur beliebige x; y 2 U gilt: jfh 2 Hj h(x) = h(y)gj 1 jHj m Folgerung: x; y 2 U beliebig, H universell, h 2 H zufallig PrH (h(x) = h(y)) m1 8.1.10 Beispiel Hashtabelle: T der Groe 3, jHj = 5 Betrachte die 20 Funktionen (Menge H): x + 0 2x + 0 x + 1 2x + 1 x + 2 2x + 2 x + 3 2x + 3 x + 4 2x + 4 (mod 5) (mod 3) 3x + 0 3x + 1 3x + 2 3x + 3 3x + 4 4x + 0 4x + 1 4x + 2 4x + 3 4x + 4 und die Schlussel 1 und 4 Es gilt: (1 (1 (4 (4 1 + 0) mod 5 mod 3 = 1 + 4) mod 5 mod 3 = 1 + 0) mod 5 mod 3 = 1 + 4) mod 5 mod 3 = 1 0 1 0 = (1 = (1 = (4 = (4 4 + 0) mod 5 mod 3 4 + 4) mod 5 mod 3 4 + 0) mod 5 mod 3 4 + 4) mod 5 mod 3 KAPITEL 8. HASHVERFAHREN 156 8.1.11 Universelles Hashing Denition 8 < h(x) = h(y) und x 6= y (x; y; h) = : 10 falls sonst Erweiterung von auf Mengen S U und G H: (x; S; h) = X (x; s; h) s2S (x; y; G ) = X (x; y; h) h2G Folgerung: H ist universell, wenn fur alle x; y 2 U (x; y; H) jHj m aufer 8.1.12 Hashing mit Verkettung der Uberl Schlussel werden in U berlauisten gespeichert h(k) = k mod 7 8.1. GESCHLOSSENE HASHVERFAHREN 0 1 2 15 2 3 4 5 157 6 Hashtabelle T Zeiger 53 12 43 5 U berlaufer 19 aufer 8.1.13 Verkettung der Uberl Suchen nach Schlussel k Berechne h(k) und U berlauiste T [h(k)] Suche nach k in der U berlauiste Einfugen eines Schlussels k Suchen nach k (erfolglos) Einfugen in die U berlauiste Entfernen eines Schlussels k Suchen nach k (erfolgreich) Entfernen aus U berlauiste ) Reine Listenoperationen Zur Analyse: Uniform Hashing-Annahme fur h: P (h(k) = j ) = m1 fur alle s 2 U und 0 j m 1 KAPITEL 8. HASHVERFAHREN 158 8.1.14 Implementation in Java Die Hashtabelle class ChainedTableEntry extends TableEntry { private ChainedTableEntry next; ... } Die Hashfunktion class ChainedHashTable \extends HashTable \{\+\\ public int h (Object key) { return key.hashCode () % getCapacity() ; } ... } Suche ein Element in der Hashtabelle public Object search (Object key) { ChainedTableEntry p = getTableEntry (h(key)); while (p != null && ! p.getKey().equals(key)) p = p.getNext (); if (p != null) return p.getValue(); else return null; } 8.1.15 Implementation: Einfugen Aufgabe: Fuge ein Element mit Schlussel key und Wert value in der U berlauiste ein (falls nicht vorhanden) public void insert (Object key, Object value) { ChainedTableEntry entry = new ChainedTableEntry(key, value); // Hole den Tabelleneintrag fuer key int k = h (key); ChainedTableEntry p = getTableEntry (k); 8.1. GESCHLOSSENE HASHVERFAHREN 159 if (p == null) { setTableEntry(k,entry); return; } // Suche nach key while (! p.getKey().equals(key) && p.getNext() != null) p = p.getNext (); // Fuege das Element ein (falls nicht vorhanden) if (! p.getKey().equals(key)) p.setNext(entry); } 8.1.16 Implementation: Loschen Hauptprozedur Entferne das Element mit Schluessel key (falls vorhanden) public void delete(Object key) { int k = h (key); ChainedTableEntry p = getTableEntry (k); setTableEntry(k, recDelete(p, key)); } Rekursion: Entferne das Element mit Schluessel key rekursiv (falls vorhanden), gibt einen Zeiger auf den Beginn der Liste p zurueck, in der key entfernt wurde public ChainedTableEntry recDelete (ChainedTableEntry p, Object key) { if (p == null) return null; if (p.getKey().equals(key)) return p.getNext(); p.setNext(recDelete(p.getNext(), key)); return p; } 8.1.17 Test-Programm public class ChainedHashingTest { public static void main(String args[]){ KAPITEL 8. HASHVERFAHREN 160 int vec[] = {12, 53, 5, 15, 2, 19, 43}; if (args.length != 0) { vec = new int [args.length]; for (int j = 0 ; j < args.length ; j++) vec [j] = Integer.valueOf(args[j]).intValue(); } Integer[] t = new Integer[vec.length]; for (int i = 0; i <= vec.length - 1; i++) t[i] = new Integer(vec[i]); ChainedHashTable h = new ChainedHashTable (7); for (int i = 0; i <= t.length - 1; i++) h.insert(t[i], null); h.printTable (); h.delete(t[1]); h.delete(t[0]); h.delete(t[6]); h.printTable(); } } Ausgabe: 0: 1: 2: 3: 4: 5: 6: -| 15 -> 43 -| 2 -| -| 53 -| 12 -> 5 -> 19 -| -| 0: -| 1: 15 -| 2: 2 -| 3: -| 4: -| 5: 5 -> 19 -| 6: -| 8.1.18 Analyse der direkten Verkettung Uniform-Hashing Annahme: alle Hashadressen werden mit gleicher Wahrscheinlichkeit gewahlt, d.h.: Pr(h(ki) = j ) = 1=m unabhangig von Operation zu Operation Mittlere Kettenlange bei n Eintragen: n=m = 8.1. GESCHLOSSENE HASHVERFAHREN Denition: Cn0 Erwartungswert fur die Anzahl betrachteter Eintrage bei erfolgloser Suche Cn Erwartungswert fur die Anzahl betrachteter Eintrage bei erfolgreicher Suche 161 KAPITEL 8. HASHVERFAHREN 162 8.2 Oene Hashverfahren 8.2.1 Situation im Oenen Hashing Idee: Unterbringung der U berlaufer an freien (\oenen") Platzen in Hashtabelle Feste Regel: Falls T [h(k)] belegt, suche anderen Platz fur k Beispiel: Betrachte Eintrag mit nachst kleinerem Index: h(k) 1 mod m 0 h(k) 1 Allgemeiner: Betrachte die Folge j = 0; : : : m 1 h(k) j mod m 8.2.2 Sondierungsfolgen Noch Allgemeiner: Betrachte Sondierungsfolge h(k) s(j; k) mod m j = 0; : : : ; m 1, fur eine gegebene Funktion s(j; k) Beispiele fur die Funktion s(j; k): s(j; k) = j s(j; k) = ( 1)j d 2j e (lineares Sondieren) 2 s(j; k) = j h0(k) (quadratisches Sondieren) (Double Hashing) m-2 m-1 8.2. OFFENE HASHVERFAHREN 163 8.2.3 Kriterien Eigenschaften von s(j; k) Folge: h(k) s(0; k) mod m; h(k) s(1; k) mod m; ... h(k) s(m 2; k) mod m; h(k) s(m 1; k) mod m ) Permutation von 0; : : : ; m 1 Beispiel: Quadratisches Sondieren 0 1 2 3 4 5 6 (11) = 4 h ( s j; k ) = 1; 1; 4; 4; 9; 9 Kritisch: Entfernen von Satzen ) als entfernt markieren - Einfugen von 4, 18, 25 - Loschen 4 - Suche 18, 25 8.2.4 Implementation: Oene Hashtabelle class OpenHashTable extends HashTable { protected int [] tag; protected int occupied; static final int EMPTY static final int OCCUPIED static final int DELETED = 0; = 1; = 2; 164 KAPITEL 8. HASHVERFAHREN /* Konstruktoren */ OpenHashTable () { super (); } OpenHashTable (int capacity) { super(capacity); tag = new int [capacity]; for (int i = 0; i < capacity; i++) { tag[i] = EMPTY; } occupied = 0; } /* Die Hashfunktion */ public int h (Object key) { int hashValue = key.hashCode () % getCapacity (); if (hashValue < 0) hashValue = hashValue + getCapacity (); return hashValue; } public int s (int j, Object key) { /* quadratisches Sondieren */ if (j % 2 == 0) return ((j + 1) / 2) * ((j + 1) / 2); else return - ((j + 1) / 2) * ((j + 1) / 2); } 8.2.5 Suchen Teilprozedur: Suche in der Hashtabelle nach Eintrag mit Schlussel key und liefere den zugehorigen Index oder -1 zuruck public int searchIndex (Object key) { int i = h(key); int j = 1; // naechster Index der Sondierungsfolge while (tag[i] != EMPTY && ! key.equals(T[i].key)) { // Pruefe neachsten Eintrag in Sondierungsfolge i = (h(key) - s(j++, key)) % capacity; if (i < 0) i = i + capacity; 8.2. OFFENE HASHVERFAHREN 165 } if (key.equals(T[i].key) && tag[i] == OCCUPIED) return i; else return -1; } Hauptprozedur: Sucht in der Hashtabelle nach Eintrag mit Schlussel key und liefert den zugehorigen Wert oder null zuruck public Object search(Object key) { int i = searchIndex (key); if (i >= 0) return T[i].value; if (i < 0) return null; } 8.2.6 Einfugen Ziel: Eintrag mit Schlussel key und Wert value einfugen public void insert (Object key, Object value) { if (occupied == capacity) // Hashtabelle voll return; // Suche nach key int i = searchIndex (key); if (i >= 0) // Schluessel bereits vorhanden return; // Suche erfolglos: Fuege key ein int j = 1; // naechster Index int i = h(key); while (tag[i] == OCCUPIED) { i = (h(key) - s(j++, key)) % capacity; if (i < 0) i = i + capacity; } T[i] = new TableEntry(key, value); tag[i] = OCCUPIED; } KAPITEL 8. HASHVERFAHREN 166 Beobachtungen: Falls i = h(k) - s(j,k), T[i].key = k und tag[i] = DELETED, dann wird nachsten Einfugen nicht nach j in der Sondierungsfolge eingefugt. Begrundung: Da tag[i] = DELETED wird k spatestens bei T [i] eingefugt 8.2.7 Test-Programm public class OpenHashingTest { public static void main(String args[]){ int vec[] = {12, 53, 5, 15, 2, 19, 43}; if (args.length != 0) { vec = new int [args.length]; for (int j = 0 ; j < args.length ; j++) vec[j] = Integer.valueOf(args[j]).intValue(); } Integer [] t = new Integer[vec.length]; for (int i = 0; i <= vec.length - 1; i++) t[i] = new Integer(vec[i]); OpenHashTable h = new OpenHashTable (7); for (int i = 0; i <= t.length - 1; i++) { h.insert(t[i], null); h.printTable (); } h.delete(t[1]); h.delete(t[0]); h.delete(t[6]); h.printTable(); } } Ausgabe (Quadratisches Sondieren): [ ] [ ] [ ] [ ] [ ] (19) (19) (19) [ ] [ ] [ ] (15) (15) (15) (15) (15) [ ] [ ] [ ] (12) [ ] [ ] [ ] (53) (12) [ ] [ ] [ ] (53) (12) (5) [ ] [ ] (53) (12) (5) (2) [ ] (53) (12) (5) (2) [ ] (53) (12) (5) (2) (43) (53) (12) (5) (2) (43) (53) (12) (5) (19) (15) (2) {43} {53} {12} (5) k beim 8.2. OFFENE HASHVERFAHREN 8.2.8 Lineares Sondieren Sondierungsfolge fur k: 167 s(j; k) = j h(k); h(k) 1; h(k) 2; h(k) 3; : : : Problem: primare Haufung (\primary clustering") 0 1 2 3 4 5 6 5 53 12 P (nachster Schlussel bei T2) = P (nachster Schlussel bei T0) = ! 1 1 2 1 + (1 )2 ! 1 1 Cn 2 1 + (1 ) Cn0 8.2.9 Quadratisches Sondieren s(j; k) = ( 1)j d 2j e 2 Sondierungsfolge fur k: h(k); h(k) + 1; h(k) 1; h(k) + 4; : : : Permutation, falls m = 4l + 3, prim KAPITEL 8. HASHVERFAHREN 168 Problem: sekundare Haufung 1 1 ! 1 + ln 1 ! 1 Cn 1 2 + ln 1 Cn0 8.2.10 Uniformes Sondieren s(j; k) = k(j ) k eine der m! Permutationen von f0; : : : ; m 1g hangt nur von k ab gleichwahrscheinlich fur jede Permutation Cn0 1 1 ! 1 1 Cn ln 1 Zufalliges Sondieren s(j; k) = von k abhangige Zufallszahl s(j; k) = s(j 0; k) moglich, aber unwahrscheinlich 8.2.11 Analyse ideales Hashing Beim idealen Hashing wird angenommen, da n Daten in einem Speicher mit m Platzen eingefugt werden, keine Loschungen vorgenommen worden sind und alle Kongurationen von nbesetzten und m n unbesetzten Speicherplatzen die gleiche Wahrscheinlichkeit 1= mn besitzen. Sei Pr die Wahrscheinlichkeit, da beim der erfolglosen Suche genau r Platze getestet werden mussen, dann gilt: 8.2. OFFENE HASHVERFAHREN 169 8 m r m < Pr = : 0n (r 1) = n frur>1m r m Begrundung: Die ersten r 1 Platze sind besetzt, der r-te Platz ist frei. Auf den restlichen m r Platzen konnen die weiteren n (r 1) besetzten Platze beliebig verteilt sein. Die erwartete Anzahl von Test betragt demnach Cn0 ! m! m r = rPr = r n (r 1) = n r r ! m! m X m r = rm n 1= n r ! m! m X m r = ((m + 1) (m + 1 r)) m n 1 = n r ! m! m X m r = (m + 1) (m + 1 r ) m n 1 = n r ! m! m X m r + 1 = (m + 1) (m n) m n = n r !X m m r + 1! m = (m + 1) (m n)= n m !n r ! m X r = (m + 1) (m n)= m n ! r m n! m+1 = (m + 1) (m n)= m n ! m n!+ 1 m+1 = (m + 1) (m n)= m n n = (m + 1) (m n)(m + 1)=(m n + 1) = (m + 1)(1 (m n)=(m n + 1) = (m + 1)=(m n + 1) m=(m n) = 1=(1 ) = 1 + + + + ::: m X m X =1 =1 =1 =1 =1 =1 =1 =1 2 3 Die durchschnittliche Anzahl der inspizierte Platze ist bei oenen Hashverfahren n Cn = 1=n X (m + 1)=(m k + 1) 1 k=0 KAPITEL 8. HASHVERFAHREN 1 1 ! 170 = m n+ 1 m 1n + 2 + m n + 3 + : : : + m 1 = m n+ 1 (H (m + 1) + H (m n + 1)) m n+ 1 ln mm +n +1 1 1 ln 1 1 mit H (m) = 1 + 1=2 + 1=3 + : : : + 1=m ln m: 8.2.12 Double Hashing Idee: Wahle zweite Hashfunktion h0 s(j; k) = j h0(k) Sondierungsfolge fur k: h(k); h(k) h0(k); h(k) 2h0(k); : : : Permutation: h0(0 k) 6= 0 und h0(k) 6 j m (h (k) relativ prim zu m) z.B. h0(k) = 1 + (k mod (m 2)) 8.2.13 Beispiel Hashfunktionen: h(k) = k mod 7 h0(k) = 1 + k mod 5 Schlusselfolge: 15, 22, 1, 29, 26 8.2. OFFENE HASHVERFAHREN 0 0 0 0 1 2 15 3 1 2 15 3 1 2 15 3 171 4 5 6 h (22) = 3 0 4 4 1 2 3 4 29 15 5 6 22 h (1) = 2 5 6 22 1 h (29) = 5 5 6 22 1 h (26) = 2 0 0 0 8.2.14 Verbesserung der erfolgreichen Suche Beispiel: Hashtabelle der Groe 11, Double Hashing mit h(k) = k mod 11 und h0(k) = 1 + (k mod (11 2)) = 1 + (k mod 9) Bereits eingefugt: 22, 10, 37, 47, 17 Noch einzufugen: 6 und 30 h(6) = 6, h (6) = 1 + 6 = 7 0 KAPITEL 8. HASHVERFAHREN 172 0 1 2 3 22 4 5 6 7 8 9 17 47 37 10 10 h(30) = 8, h (30) = 1 + 3 = 4 0 0 1 2 22 3 4 5 47 37 6 7 6 8 17 8.2.15 Verbesserung der erfolgreichen Suche Einfugen: k trit in T [i] auf kalt, d.h.: i = h(k) s(j; k) = h(kalt) s(j 0; kalt) kalt bereits in T [i] gespeichert Idee: Suche freien Platz fur k oder kalt Zwei Moglichkeiten: (M1) kalt bleibt in T [i] betrachte neue Position h(k) s(j + 1; k) fur k (M2) k verdrangt kalt betrachte neue Position h(kalt) s(j 0 + 1; kalt) fur kalt Falls (M1) oder (M2) trit auf einen freien Platz dann trage entsprechenden Schlussel ein fertig, sonst verfolge (M1) oder (M2) weiter 8.2.16 Brent und Binarbaum Sondieren Brent's Verfahren: verfolge nur (M1) Binarbaum Sondieren 9 10 10 8.2. OFFENE HASHVERFAHREN 173 k weicht aus k trit auf k k trit auf k k k k trit auf k 00 000 weicht aus fertig 0000 CnBrent 1 + 2 + 4 + 15 + < 2:5 Cn0 1 1 CnBinarbaum 1 + 2 + 4 + 15 + < 2:2 8.2.17 Beispiel : 0 weicht aus fertig 000 k weicht aus fertig 00 k weicht aus k trit auf k 0 3 4 3 4 Hashfunktionen: h(k) = k mod 7 h0(k) = 1 + k mod 5 Schlusselfolge: 12, 53, 5, 15, 2, 19 KAPITEL 8. HASHVERFAHREN 174 0 1 2 3 4 5 6 53 12 h(5) = 5 belegt mit k0 = 12 Betrachte: h00(5) = 1 ) h(5) 1 h0(5)0 = 5 1 = 4 belegt! h (12) = 3 ) h(12) 1 h (12) = 5 3 = 2 frei ) 5 verdrangt 12 von seinem Platz 8.2.18 Verbesserung der erfolglosen Suche Suche nach k: k0 > k in Sondierungsfolge: ) Suche erfolglos Einfugen (Ordered Hashing) kleinere Schlussel verdrangen groere Schlussel Invariante: Alle Schlussel in der Sondierungsfolge vor k sind kleiner als k (aber nicht notwendigerweise aufsteigend sortiert) Probleme: Verdrangungsproze kann \Kettenreaktion" auslosen k0 von k verdrangt: Position von k0 in Sondierungsfolge? ) s(j; k) s(j 1; k) = s(1; k), 1 j m 8.2.19 Beispiel: Ordered double Hashing h(0 k) = k mod 7 h (k) = 1 + k mod 5 Schlusselfolge: 15, 2, 43, 4, 8 0 1 2 15 2 3 4 43 5 6 h(43) = 1, h0(43) = 4 ) h(43) h0(43) = 1 40= 4 (mod 7) Einfugen von 4: 4 verdrangt 43! nachster Platz: 4 h (43) = 4 4 = 0 (mod 7) Einfugen von 8: 8 verdrangt 15! nachster Platz: h(15) h0(15) = 1 1 = 0 (mod 7). 15 verdrangt 43! nachster Platz: 0 h0(43) = 0 4 = 3 (mod 7) 8.2. OFFENE HASHVERFAHREN 8.2.20 Implementation: Suchen 175 Prozedur sucht in der Hashtabelle nach Eintrag mit Schlussel key und liefert den zugehorigen Index oder -1 zuruck public int searchIndex (Object key) { Orderable orderableKey = (Orderable) key; int i = h(key); int j = 1; // naechster Index in Sondierungsfolge while (tag[i] != EMPTY && orderableKey.greater(getOrderableKey(i)) && j <= getCapacity()) { // Pruefe naechsten Eintrag in Sondierungsfolge i = (h(key) - s(j++, key)) % getCapacity(); if (i < 0) i = i + getCapacity(); } if (tag[i] == OCCUPIED && orderableKey.equals(getOrderableKey(i))) { return i; } else { return -1; } } 8.2.21 Implementation Einfugen Schlussel key Wert value public void insert (Object key, Object value) { if (occupied == getCapacity()) return; Orderable orderableKey = (Orderable) key; TableEntry entry = new TableEntry (key, value); int i = h(key); int j = 0; // Anzahl inspizierter Eintrage while (tag[i] != EMPTY && ! orderableKey.equals(getOrderableKey(i)) && (j++ < getCapacity())) { if (orderableKey.less(getOrderableKey(i))) { if (tag[i] == DELETED) KAPITEL 8. HASHVERFAHREN 176 break; else { // verdraenge den groesseren Schuessel TableEntry help = getTableEntry(i); setTableEntry(i, entry); entry = help; orderableKey = (Orderable) entry.getKey(); } } i = (i - s(1,orderableKey)) % getCapacity(); if (i < 0) i = i + getCapacity(); } if (tag[i] != OCCUPIED) { setTableEntry(i,entry); tag[i] = OCCUPIED; occupied++; } } Kapitel 9 Graphalgorithmen 177 KAPITEL 9. GRAPHALGORITHMEN 178 9.1 Graphen (Gerichtete) Graphen: G = (V; E ) bestehen aus Knoten V und Kanten E Beispiel: und eignen sich zur Reprasentation z.B. von Bus- und Straenbahnverbindun- gen der Stadt Freiburg. Reprasentation: Man unterscheidet zwei grundlegend verschiedene Implementationen von Graphen. Die Adjazenzliste: An ein Array von Knoten wird fur jeden Eintrag eine Liste der Nachbarn angehangt. Die Adjazenzmatrix: Der Eintrag an der Stelle (i; j ) der Matrix aus Wahrheitswerten gibt an, ob zwischen dem Knoten i und dem Knoten j eine Kante existiert oder nicht. 1 2 3 0 4 9.1.1 Implementation Knoten: class Knoten { Knoten next; int n; Knoten(int n) { next = null; this.n = n; } Knoten(int n, Knoten k) { next = k; 9.1. GRAPHEN this.n = n; } } Graph: class Graph { public static void main(String args[]) { Adliste Al = new Adliste(10,0.3); Admatrix Am = new Admatrix(10,0.3); System.out.println(Am); System.out.println(Al); } } 9.1.2 Implementation Adjazenzmatrix Konstruktion: Kantendichte gema vorgegebener Dichte ausgewurfelt class Adliste { Knoten ad[]; Adliste(int n, double Dichte) { int i; int j; ad = new Knoten[n]; for(i=0;i<ad.length;i++) { for (j=0;j<ad.length;j++) { if (Math.random() < Dichte) { ad[i] = new Knoten(j,ad[i]); } } } } Ausgabe: public String toString() { StringBuffer st = new StringBuffer(""); int i; Knoten j; for(i=0;i<ad.length;i++) { for (j=ad[i];j!=null;j=j.next) { st.append(j.n+" "); 179 KAPITEL 9. GRAPHALGORITHMEN 180 } st.append("\n"); } return st.toString(); } } 9.1.3 Implementation Adjazenzliste Konstruktion: Kantendichte gema vorgegebener Dichte ausgewurfelt class Admatrix { boolean ad[][]; Admatrix(int n, double Dichte) { int i; int j; ad = new boolean[n][n]; for(i=0;i<n;i++) { ad[i] = new boolean[n]; for(j=0;j<n;j++) { if (Math.random() < Dichte) ad[i][j] = true; } } } Ausgabe: public String toString() { StringBuffer st = new StringBuffer(""); int i; int j; for (i=0;i<ad.length;i++) { for(j=0;j<ad[i].length;j++) { st.append(ad[i][j] + " "); } st.append("\n"); } return st.toString(); } } 9.1. GRAPHEN 9.1.4 Tiefensuche Hauptschleife: public void DFS() { boolean visited[] = new boolean[ad.length]; for (int i=0;i<ad.length;i++) { if (!visited[i]) DFS(i,visited); } } Rekursion: void DFS(int i, boolean visited[]) { System.out.println("Visiting node " +i); visited[i] = true; for (Knoten j=ad[i];j!=null;j=j.next) { if (!visited[j.n]) DFS(j.n,visited); } } 9.1.5 Topologische Sortierung public void topsort() { int [] indeg = new int[ad.length]; for(int i=0;i<ad.length;i++) { for (Knoten j=ad[i];j!=null;j=j.next) { indeg[j.n]++; } } Queue Q = new Queue(ad.length); for(int i=0;i<ad.length;i++) { if (indeg[i] == 0) Q.enqueue(i); } int lfdNr = 0; while (!Q.empty()) { int i = Q.dequeue(); System.out.println(i); lfdNr++; for (Knoten j=ad[i];j!=null;j=j.next) { 181 KAPITEL 9. GRAPHALGORITHMEN 182 indeg[j.n]--; if (indeg[j.n] == 0) Q.enqueue(j.n); } } if (lfdNr != ad.length) System.out.println("Graph hat einen Zyklus"); } Teil II Aufgaben 183 Kapitel 10 Theorie Aufgabe 1: Die Fibonacci-Zahlen sind fur n 2 f0; 1; 2 : : :g wie folgt rekursiv deniert: F (0) = 0 F (1) = 1 F (n) = F (n 1) + F (n 2) , fur n 2. Zeigen Sie z.B. durch vollstandige Induktion: (a) Fur n 6 ist F (n) 2n=2. (b) Fur n 0 gilt F (0) + : : : + F (n) = F (n + 2) 1. (c) Fur n 1 ist F 2(n) = F (n 1)F (n + 1) + ( 1)n+1. Losung zur Aufgabe 1: (a) I.-Anfang F (6) = 8 = 26=2. F (7) = 13 > 27=2 = 11.31. I.-Schlu: F (n) = F (n 1) + F (n 2) 2(n 1)=2 + 2(n 2)=2 2 2(n 2)=2 = 2n=2: (b) I.-Anfang:PF (0) = F (2) 1 P I.-Schlu: ni=0 F (i) = F (n) + ni=01 F (i) = F (n + 1) + F (n) 1 = F (n + 2) 1: (c) I.-Anfang: F (1) = F (0)F (2) + 1 = 1. I.-Schlu: F 2(n) = F (n)(F (n 1)+ F (n 2)) = F (n)F (n 1)+ F (n 1)2 ( 1)n = F (n 1)(F (n)+ F (n 1)) ( 1)n = F (n 1)F (n +1)+( 1)n+1: 185 KAPITEL 10. THEORIE 186 Aufgabe 2: Im Folgenden wollen wir uns einer suchfreien Losung des n{Dameproblems fur n > 3 annahern. Das Brett sei in der Anzahl von Zeilen und Spalten von 1 bis n numeriert. Desweiteren denieren wir ein Springermuster S an der Stelle (i; j ) als die Menge derjenigen Felder, die ein Springer bei stetem Rechtshochsprung belegt, d.h. S(i; j ) = f(i; j ); (i 1; j + 2); (i 2; j + 4); (i 3; j + 6) : : :g: (a) Zeigen Sie: Zwei Damen innerhalb eines Springermusters behindern sich gegenseitig nicht. Hinweis: Ein Diagonalkonikt ist genau dann gegeben, wenn die absolute Dierenz der Zeilen gleich der absoluten Dierenz der Spalten ist. (b) Zeigen Sie, da fur alle geraden n aus fnj(n 2) mod 6 6= 0g also fur alle n aus f4; 6; 10; 12; 16; : : :g die Belegung S (n=2; 1) [ S (n; 2) eine Losung des n{ Dameproblems ist. Fur n = 6 sieht die Losung wie folgt aus: 1 2 3 4 5 6 1 2 3 4 5 6 Losung zur Aufgabe 2: (a) Seien zwei Positionen (i k1; j + 2k1) und (i k2; j + 2k2) fur k1 < k2 gegeben, dann folgt aus der Annahme eines Diagonalkoniktes ji k1 (i k2)j = jj + 2k1 (j + 2k2)j unmittelbar, da k1 = k2 gilt, Widerspruch. Da sowohl i k1 = i k2 als auch j +2k1 = j +2k2 die Gleichheit von k1 und k2 erzwingen, gibt es genausowenig einen Zeilen- oder Spaltenkonikt in S (i; j ). (b) Sei (i; j ) aus S (n=2; 1) und (l; m) aus S (n; 2). Da j ungerade und m gerade ist, gibt es keinen Spaltenkonikt der Mengen S (n 2; 1) und S (n; 2). Da i n=2 und l > n=2 gilt, gibt es genausowenig einen Zeilenkonikt innerhalb der zwei Mengen. Bleibt z.z., da es auch keinen Konikt auf der Diagonalen gibt. Dazu betrachte d1 = l i und d2 = jm j j. Falls d1 gleich d2 ist, gibt es einen 187 Konikt auf der Diagonalen. Annahme, d1 = d2, also entweder l i = m j oder l i = j m. Es gibt ein p mit l = n p und m = 2+2p und auerdem ein q mit i = n=2 q und j = 1 + 2q. Im ersten Fall erhalten wir somit n=2 1 = 3p 3q oder n 2 = 6(p q) im Widerspruch zur Vorraussetzung. Im zweiten Fall ergibt sich n=2 + 1 = p q im Widerspruch zur Voraussetzung. Bemerkungen: (a) Fur ein ungerades n aus fnj(n 3) mod 6 6= 0g also aus f5; 7; 11; 13; 17; : : :g lat sich eine nahezu identische Losung zu konstruieren: Beispiel n = 7: 1 1 2 3 4 5 6 7 2 3 4 5 7 6 (b) Durch Abandern der letzten drei Damen in der Konstellation S (n=2; 1) [ S (n 1; 2) lat sich eine Losung fur ein gerades n aus fnj(n 2) mod 6 = 0g bzw. f8; 14; 20 : : :g nden. Beispiel n = 8. 1 2 3 4 5 6 7 8 1 2 3 4 6 5 7 8 KAPITEL 10. THEORIE 188 (c) Das Schema fur die ubrig gebliebene Menge ungerader n aus fnj(n 3) mod 6 = 0g bzw. aus f9; 15; 21; : : :g entsteht aus der Erganzung zu S (bn=6c; 1) [ S(n; n=3 + 1) und Streckung von S(2n=3; 2) an der Spalte n=3 + 1. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 2 3 4 5 6 7 8 10 11 12 13 14 15 9 Aufgabe 3: Da es ein Auto in Freiburg gibt, das nicht wei ist, und dieses Auto wiederum nicht unmittelbar schwarz sein mu, ist eine schmerzliche Erkenntnis einer anonymen Umfrage. Wir wollen uns deshalb lieber auf ein kontrastreiches schwarz-wei Problem beschranken. Zeigen Sie, da, wie im Beispiel der Abbildung 10.1, jede Aufteilung der Ebene durch n verschiedene Linien mit schwarz und wei eingefarbt werden kann, so da keine zwei Regionen gleicher Farbe eine Kante teilen. Hinweis: Auch hier lat sich das Prinzip der vollstandigen Induktion im Beweis nutzen. Losung zur Aufgabe 3: Die Hypothese ist fur n gleich eins wahr: Farbe eine Seite schwarz, die andere Seite wei. Nun nehme an, da n Linien in der Ebene gegeben sind und wir n 1 Linien auswahlen. Diese Auswahl lat sich aufgrund der Induktionshypothese 189 Abbildung 10.1: Die Farbung einer mit 6 Linien aufgeteilten Ebene mit schwarz und wei farben. Nun nehmen wir die n-te Linie hinzu. Darauf hin invertieren wir alle Farben auf der einen Seite der Linie. Angenommen, zwei adjazente (nebeneinanderliegende) Flachen hatten die gleiche Farbe. Falls die gemeinsame Kante nicht die n-te Linie ist, so ergibt sich ein Widerspruch zur Induktionshypothese, da entweder keine oder beide Farben gewechselt wurden. Falls die n-te Linie die Flachen teilt, hatten sie vorher zwar die gleiche, nun aber eine unterschiedliche Farbe. Bemerkung: Farbungsprobleme haben eine lange Tradition. Das Farben der Knoten eines planaren Graphen mit vier Farben ist immer moglich. Mit drei Farben handelt es sich um eines der schwersten Probleme der Informatik. Aufgabe 4: Eine Schleifeninvariante ist der Schlussel zum Beweis der Korrektheit eines iterativen Algorithmus. Dabei nutzen wir den Index j um den Variableninhalt nach der j {ten Iteration zu beschreiben. Als Beispiel betrachten wir die Berechnung der Fakultat einer nicht negativen ganzen Zahl m: n = 1; while (m > 1) { n *= m; m--; } Die Schleifeninvariante ist: nj = Qm k=m j +1 k: Finden Sie die Schleifeninvarianten der folgenden Programme! (a) Summation von Werten im Array A[0::n s = 0; for (i=0;i<n;i++) s += A[i]; (b) Maximum im Array A[0::n 1]: 1]: KAPITEL 10. THEORIE 190 m = A[0]; for (i=1;i<n;i++) { if (A[i] > m) m = A[i]; } (c) Schnelle Multiplikation y z: x = 0; while (z>0) { if (z % 2 == 1) x += y; else { y *= 2; z /= 2; } } (d) Exponentation yz : x = 1; while (z>0) { x *= y; z--; } (e) Schnelle Exponentation yz : x = 1; while (z>0) { if (z % 2 == 1) x *= y; else { y *= y; z /= 2; } } Losung zur Aufgabe 4: (a) sj = Pji=01 A[i]. (b) mj = maxfA[0]; : : : ; A[j ]g (c) yj zj + xj = y0z0 (d) xj yjzj = y0z0 (e) xj yjzj = y0z0 Aufgabe 5: Ben Bitdiddle hat sich ein neues Multiplikationsverfahren fur zwei naturliche Zahlen ausgedacht, das er uns an dem Beispiel der Zahlen 9 und 14 gerne vorfuhrt: 191 9 14 ||||| 4 28 ||||| 2 56 1 112 |||||{ total = 126 Er erklart sein Verfahren wie folgt: Die linke Seite wird ohne Rest halbiert und die rechte Seite verdoppelt. Dabei werden die Zeilen mit gerader linker Seite gestrichen und die nicht gestrichenen rechten Seiten aufaddiert. (a) Beschreiben Sie den Algorithmus von Ben Bitdiddle durch ein Fludiagramm und durch ein Struktogramm. (b) Beschreiben Sie das Verfahren durch Angabe eines Java Programmes mult. (c) Beweisen Sie die Korrektheit des Ansatzes. (d) Wieviel Iterationen benotigt Ben Bitdiddles Verfahren? Ist es besser als das Verfahren der Schulmethode? Losung zur Aufgabe 5: (a) Fludiagramm und Struktogramm fur Bens Algorithmus: Siehe Abbildungen. (b) public class Mult { public static int mult (int x, int y) { int total = 0; while (x>0) { if (x%2 == 1) total += y; x /= 2; y *= 2; } return total; } public static void main (String args[]) { int a = new Integer(args[0]).intValue(); int b = new Integer(args[1]).intValue(); System.out.println("Das Produkt von "+a+ KAPITEL 10. THEORIE 192 Start Eingabe: x,y total := 0 Nein x>0 Ja x gerade Nein total := total+y Ausgabe: total Ja Stop x:= x/2 y:= y*2 Eingabe: x,y total := 0 x>0 x gerade Ja Nein total := total+y x:= x/2 y:= y*2 Ausgabe: total 193 " und " +b+" ist "+ mult(a,b)); } } (c) Wir kurzen total mit z ab und erhalten die Invarianz P : x 0 und z + xy = ab: Sicher impliziert die Vorbedingung fx = a; y = b; z = 0g die Invarianz. Weiterhin impliziert (x 0) und (nicht x > 0) da x = 0 ist. Eingesetzt in P ergibt dies z = ab. Bleibt zu zeigen, da P bei einmaliger Ausfuhrung der Schleife while (x>0) { if (x%2 == 1) z += y; x /= 2; y *= 2; } richtig bleibt. Durch die Division durch 2 bleibt x positiv. Falls x gerade ist, bleiben xy und z unverandert, also gilt P . Falls x ungerade ist, wird z += y ausgefuhrt. Dies ist notwendig, da durch die Division ohne Rest von x durch 2 in der Multiplikation mit y einmal der Wert 0 die Werte nach dem Schleifendurchlauf. Der y verloren geht. Seien x0; y0 und z Wert x hat die Darstellung 2x0 + 1. Damit gilt: xy + z = (2x0 + 1)y + z = x0y0 + y + z = x0y0 + z0. Zum Nachweis der totalen Korrektheit ist noch zu zeigen, da das Programm terminiert. Betrachtet man den Rumpf der while Schleife, so stellt man fest, da der Wert von x immer (um mindestens 1) abnimmt. Das Programm halt, wenn x den Wert 0 erreicht hat. (d) Die Anzahl der Schleifeniterationen ist durch die Lange der Binardarstellung von a also durch dlog(a+1)e festgelegt. Es ist dadurch etwas besser als die Schulmethode. Aufgabe 6: Java kennt mehrere Schleifen- und Fallunterscheidungskonstrukte, in denen Bedingungen gepruft und ein bzw. mehrere Anweisungsteile ausgefuhrt werden. Zeigen Sie, da es sich hier um sogenannten syntaktischen Zucker handelt, und fuhren Sie die folgenden Anweisungen auf eine einfache while Schleife zuruck: (a) for(int i=anfang;i<grenze;i++) { <B> } (b) do { <B> } while (A) KAPITEL 10. THEORIE 194 (c) if (A) { <B> } else { <C> } (d) switch (A) { case A1:<B> break; case A2:<C> break; default: Hinweis: In (c) und (d) empehlt es sich, boolesche Variablen einzufuhren. <D>; } Losung zur Aufgabe 6: (a) int i=anfang; while (i<grenze) { <B> i++; } (b) <B> while (A) { <B> } (c) (d) boolean b = A, c = !b; while (b) { <B> b = false; } while (c) { <C> c = false; } boolean b while (b) while (c) while (d) = { { { (A==A1), c = (A==A2), d = !b && !c; <B> b = false; } <C> c = false; } <D> d = false; } Aufgabe 7: Petra Flipop und Franz Hacker wollen das Abmeproblem (eine Variante des in der Vorlesung besprochenen Umfullproblems ) losen. Sie haben zwei Stabe der Lange 15cm und 42cm und wollen damit ein Seil der Lange 1m abmessen. Sie versuchen es immer wieder, doch die beiden bemuhen sich vergeblich. Bieten Sie Ihre Hilfe an, um den beiden zu zeigen, da die gestellte Aufgabe unlosbar ist. (a) Geben Sie Losungen des Abmeproblems fur die Lange 99cm, fur die Lange 27cm und fur die Lange 3cm an. Da alle erzeugbaren Zahlen ein Vielfaches von 3 sind und 3 wiederum der grote gemeinsame Teiler von 15 und 42 ist, liegt die Vermutung nahe, da die abmebaren Langen immer ein Vielfaches des groten gemeinsamen Teilers sind. (b) Zeigen Sie, da es fur jede Zahl der Form 3k, k 2 IN, zwei ganze Zahlen x und y gibt, so da 3k = 15x + 42y gilt. Hinweis: Nutzen Sie dabei die gewonnenen Erkenntnisse des U bungsblattes 4, Aufgabe 3. (c) Zeigen Sie, da keine andere Zahlen als die von der Form 3k, werden, d.h. alle erzeugten Zahlen durch 3 teilbar sind. k2 , erzeugt IN 195 Losung zur Aufgabe 7: (a) 99 = 1 15 + 2 42, 27 = ( 1) 15 + 1 42, 3 = 3 15 + ( 1) 42 (b) Da 3 = ggT (15; 42) gilt, gibt es nach Aufgabe 3, Blatt 4 zwei Zahlen x und y mit 3 = 15x + 42y. Somit gibt es auch zwei Zahlen x0 = kx und y0 = ky0 mit 3k = 15x0 + 42y0. (c) Da 3 als groter gemeinsamer Teiler sowohl 15 als auch 42 teilt, gilt 15x + 42y = 3(5x + 14y). Damit sind auch alle erzeugten Zahlen durch 3 teilbar (setze k = 5x + 14y). Aufgabe 8: Die Landau'sche O-Notation ist sozusagen das A und O in der Algorithmenanalyse, da sie es erlaubt, Laufzeit- bzw. Speicherplatzfunktionen in Klassen einzuteilen. Fomal bedeutet f (n) 2 O(g(n)), da es ein reelles c > 0 und ein naturliches n0 gibt, so da fur alle n n0 der Wert f (n) kleiner oder gleich cg(n) ist. (a) Zeigen Sie durch die Wahl geeigneter Konstanten c und n0, da (n + 1)2 in der Klasse O(n2) liegt. (b) Es gilt die folgende Beziehung: Falls der Grenzwert von f (n)=g(n) fur n gegen unendlich durch eine Konstante c beschrankt ist (wir schreiben limn!1 f (n)=g(n) c), so gilt f (n) 2 O(g(n)). Zeigen Sie unter Verwendung dieses Sachverhaltes abermals (n + 1)2 2 O(n2). (c) Die Regel von L'Hospital besagt: Falls der Grenzwert f 0(n)=g0(n) fur n gegen unendlich existiert (f 0 ist die Ableitung von f ), so ist er gleich dem Grenzwert von f (n)=g(n). Zeigen Sie mit Hilfe dieser Regel letztmalig (n + 1)2 2 O(n2). Losung zur Aufgabe 8: (a) Zu zeigen ist (n + 1)2 cn2 oder (c p1)n2 2n p 1 0. Wir wahlen c = 2. Die Nullstellen von n2 2n 1 sind p2 + 1 und 2 + 1. Die Parabel ist nach oben geonet. Also ist fur n n0 = d 2 + 1e der Wert (n + 1)2 2n2 und demnach (n + 1)2 2 O(n2). (b) n2 + 2n + 1 = lim n2(1 + 2=n + 1=n2) = lim (1+2=n+1=n2) = 1: lim n!1 n!1 n!1 n2 n2 KAPITEL 10. THEORIE 196 (c) Da nlim !1 2=2 = 1 gilt, ist 2n + 2 = 1 lim n!1 2n und somit n2 + 2n + 1 = 1: lim n!1 n2 Aufgabe 9: (a) (b) (c) (d) Zeigen Sie: 3n4 n2 = O(n4). Zeigen Sie: 2n = o(n!) (Hinweis n! (n=2)n=2) . Zeigen Sie: Fur alle k ist nk = o(2n): Zeigen Sie: Es seien p1 und p2 Polynome vom Grad d1 bzw. d2, wobei die Koezienten von nd1 und nd2 positiv sind. Dann gilt: d = d ) p = (p ); d < d ) p = o(p ); d > d ) p = !(p ): (e) Zeigen Sie: Fur alle k > 0 und > 0 gilt logk n = o(n) (f) Zeigen Sie: 2n= = o(2n): 1. 2. 3. 1 2 1 2 1 2 1 2 1 2 1 2 2 (g) Finden Sie eine Funktion, in denen beide Denitionen von aus der Vorlesung voneinander abweichen. (h) Fur monoton steigende Funktionen f 6 0 sind beide Denitionen fur Gro-O aus der Vorlesung aquivalent. Losung zur Aufgabe 9: (a) 3n2 n2 = lim 3 1=n2 = 3: lim n!1 n!1 n4 1 197 (b) n n 2 2 n=2 nlim !1 n! nlim !1 (n=2)n=2 = nlim !1(8=n) = 0: (c) Es gilt limn!1 nk =2n = 0 durch k faches Anwenden der Regel von L'Hospital. 1 a ni , Polynom p (n) = Pd2 b ni d = d folgt (d) Polynom p1(n) = Pdi=0 i 2 1 2 i=0 i limn!1 p1(n)=p2(n) = ad1 =ad2 und limn!1 p2(n)=p1(n) = ad2 =ad1 . Damit ist p1 = (p2). Falls d1 < d2, dann limn!1 p1(n)=p2(n) = 0. Damit p1 = o(p2). Rest analog. (e) Nach der Regel von L'Hospital gilt: k! = = lim k(log n) 0 = nlim k !1 n n!1 n (f) n=2 =2n = nlim !1 2 k 1 (log n) : = nlim !1 n k n=2 nlim !1 1=2 = 0: (g) Denition f (x) = 1 fur x gerade, f (x) = n fur x ungerade. Einmal f = (1) und einmal f = (n) (h) Cormen ! Ottmann: Sei c > 0, n0 2 N mit g(n) cf (n). Setze a = c und b = maxff (n) j n n0g Fur n n0 ist g(n) b a f (n)+ b. Fur n > n0 ist g(n) cf (n) a f (n) + b Ottmann ! Cormen: Seien a; b 0 mit g(n) a f (n) + b fur alle n 2 N . Da f 6 0, existiert n0 2 N mit f (n0) > 0. Setze c = a + b=f (n0). Fur n n0 ist cf (n) = af (n) + bf (n)=f (n0) a f (n) + b g(n) Beachte, da f (n)=f (n0) 0, da f monoton. Aufgabe 10: Gegeben sei ein lineares Feld a[1..n] positiver reeller Zahlen. Gegeben seien auerdem eine Funktion g die als Werte 0 oder 1 liefert, und die folgende Funktion gtest : int gtest(int li, int re) { if (li > re) return 0; int m = (li+re)/2; return gtest(li,m-1)+g(a[m])+gtest(m+1,re); } KAPITEL 10. THEORIE 198 a) Beschreiben Sie, welches Resultat die Funktion beim Aufruf gtest(1; n) fur ein gegebenes Feld a liefert. b) Ermitteln Sie groenordnungsmaig die Anzahl der Additionen bei der Ausfuhrung eines Aufrufs von gtest(li; re) im schlimmsten Fall, in Abhangigkeit von j re li j, mit Hilfe einer Rekursionsformel. c) Geben Sie in Java ein alternatives (iteratives) Verfahren zur Ermittlung des Funktionswertes gtest an; verwenden Sie denselben Funktionskopf. Losung zur Aufgabe 10: (a) Pni=1 g(a[i]) (b) T (n) = 2T (n=2) + O(1). Fur n = 2k : T (2k ) = 2T (nk 1) + c = 4T (nk 2) + 2c = cnT (20) 2 O(n). (c) static in gtest2 (int li, int re) { int sum = 0; for (int i=0; i < 1000; i++) { sum += g(a[i]); } } Aufgabe 11: Erstellen Sie zwei Pseudo-Code Programme zur iterativen Berechnung der folgenden verallgemeinerten Binomialkoezienten. 1 1 1 5 1 4 1 3 2 4 7 8 11 15 16 Das allgemeine Bildungsgesetz lautet d0 = 1; dd = 2d und hd = d h 1 + hd 11 . a) Das erste Programm verwende einen Stapel, der verkettet gespeichert, also uber Zeiger realisiert wird. b) Das zweite Programm verwende ein Berechnungsschema, das mehrfache Berechnung von gleichen Teilresultaten vermeidet. 199 Losung zur Aufgabe 11: Anstatt einer Stapellosung geben wir alternativ eine rekursive Losung an. class Binomial { public static int ueber (int d, int h){ int[] tabelle = new int[d+2]; // Tabelle ZwischenErg tabelle[0] = 1; for (int i=0; i<=d; i++){ // Hoehe Pascal Dreieck int hilf2 = 1; for (int j=1; j<=i; j++){ // Breite Pasc Dreieck if (j==i) tabelle[j] = 2*hilf2; else { // Sonderfall j=i int hilf3 = tabelle[j]; tabelle[j] += hilf2; // berechne neuen Eintrag hilf2 = hilf3; } } } return tabelle[h]; } public static int hoch (int d){ if (d == 0) return 1; else return (2*hoch(d-1)); } public static int ueber2 (int d, int h){ if (h == 0) return 1; else if (d == h) return hoch(d); else return ueber2 (d-1,h) + ueber2 (d-1,h-1); } public static void main (String[] argv){ int d = Integer.parseInt(argv[0]); int h = Integer.parseInt(argv[1]); int ergebnis = ueber (d,h); System.out.println (ergebnis); ergebnis = ueber2 (d,h); System.out.println (ergebnis); } } 200 KAPITEL 10. THEORIE Aufgabe 12: Auswahlsort ist ein einfacher Vorlaufer von Heapsort. Gegeben sei ein Elementarray A von n vergleichbaren Objekten. Der Algorithmus sucht fur j = 1; : : : ; n 1 in dem Array von der Stelle j aus das kleinste Element und tauscht es an die Stelle j . (a) Bestimmen Sie die Anzahl der Vergleiche im best, worst und average case. (b) Besimmen Sie die Anzahl von Bewegungen im best, worst und average case. Losung zur Aufgabe 12: Vergleiche Ottmann und Widmayer Algorithmen und Datenstrukturen Seite 66 . (a) Cmin(n) = Cmax(n) = Cmit(n) = Pni=11(n i) = Pni=11 i = (n2) (b) Cmin(n) = Cmax(n) = Cmit(n) = 3(n 1) Aufgabe 13: Denieren Sie die Begrie terminierend, determiniert und deterministisch. Losung zur Aufgabe 13: Ein Algorithmus heit determiniert, falls er bei gleichen Eingaben und Startbedingungen stets dasselbe Ergebnis liefert. Ein Algorithmus terminiert, falls er fur alle Eingaben nach endlich vielen Schritten ein Resultat liefert. Ein Algorithmus heit deterministisch, wenn er zu jedem Zeitpunkt seiner Ausfuhrung hochstens eine Moglichkeit der Fortsetzung besteht; ansonsten heit er nichtdeterministisch. Aufgabe 14: Beweisen Sie die Additionsregel O(f ) + O(g) = O(maxff; gg) und die Multiplikationsregel O(f ) O(g) = O(f g). Worin besteht der praktische Nutzen dieser Regeln? Losung zur Aufgabe 14: Voraussetzung: 9c1; c2 2 R+ und n1; n2 2 N mit T1(n) c1f (n) fur alle n n1 und T2(n) c2f (n) fur alle n n2. Additionsregel: T1(n) + T2 (n) c maxff (n); g(n)g fur c = c1 + c2 und n0 maxfn1; n2g. Multiplikationsregel: T1(n)T2(n) cf (n)g(n) fur c = c1c2 und n0 maxfn1; n2g. Additionsregel fur die Komplexitat der Hintereinanderausfuhrung der Programme. Multiplikationsregel fur die Komplexitat von ineinandergeschachtelten Schleifen. 201 Bemerkung: Denition mit g cf stimmt uberein mit g af + b, falls f 6 0 monoton. Aufgabe 15: Wir betrachten Funktionen von den naturlichen Zahlen in die nicht-negativen Zahlen. (a) Welche Funktionen liegen in (1)? (b) Wann gilt f (n) 2 O(bf (n)c)? (c) Seien a, b, c reell mit 1 < b; 1 a; c. Zeigen Sie: logb(an + c) 2 (log2 n) (d) Seien f1; f2; : : : Funktionen in O(g). Gilt dann n h(n) = X fi(n) 2 O(ng(n))? i=1 (e) Sei P (n) ein Polynom vom Grad k. Zeigen Sie: O(log P (n)) = O(log n). p (f) Zeigen Sie: (log n)3 2 O( 3 n) und allgemein (log n)k 2 O(n) fur k; > 0. Losung zur Aufgabe 15: Die Losung der Aufgabenteile (a)-(d) ndet sich in Rolf Klein Algorithmische Geometrie, Addison-Wesley, S. 48. In Teil (e) gilt P (n) = O(nk ) und damit O(log P (n)) = O(log nk ) = O(k log n) = O(log n). Der erste Teil von (f) ist eine Folgerung des zweiten (k = 3, = 1=3). Den zweiten Teil beweist man am besten mit Hilfe der Regel von L'Hospital. (log n) = k lim (log n) = lim n!1 n n!1 n k(k 1) lim (log n)k 1 = : : : = k! lim (log n)k 1 = 0 2 n!1 n k n!1 n k Aufgabe 16: k 1 Sie haben die naive Schulmethode zur Multiplikationen von Matrizen in Rnn kennengelernt. Wir nehmen zur Vereinfachung an, da n eine 2er Potenz ist. (a) Wie viele Multiplikationen und Additionen sind dazu notwendig? Geben Sie jeweils die Groenordnung in der O-Notation an. 202 KAPITEL 10. THEORIE (b) Strassen hat 1969 einen sehr eleganten Ansatz zur gefunden, die Laufzeit der Schulmethode zu unterbieten. Er benutzt Divide-and-Conquer und zerteilt die Matrix jeweils in vier n=2 n=2 Blocke. Seien diese fur die Matrix A mit A11; A12; A21; A22 bezeichnet. Zur Berechnung von C = AB erzeugt er die sieben Hilfsterme m1 = (A12 A22)(B21 + B22), m2 = (A11 + A22)(B11 + B22), m3 = (A11 A21)(B11 + B12), m4 = (A11 + A12)B22, m5 = A11(B12 B22), m6 = A22(B21 B11), m7 = (A21 + A22)B11 und fugt sie wie folgt zusammen: C11 = m1 + m2 m4 + m6, C12 = m4 + m5, C21 = m6 + m7, C22 = m2 m3 + m5 m7. Berechnen Sie die Groenordnung der Anzahl von Elementaroperationen (Additionen und Multiplikationen), indem Sie eine Rekursionsgleichung aufstellen und das Mastertheorem anwenden. Losung zur Aufgabe 16: In einer Rekursion haben wir 7 Multiplikationen und 18 Additionen/Subtraktionen. Wir haben die folgende Rekursionsgleichung fur die Anzahl der Ringoperationen in R: T (1) = 1 und T (n) = 7T (n=2) + 18n2 fur alle n = 2k mit k > 1. Da 7 > 22 ergibt sich nach dem Mastertheorem mit f (n) = 18n2 die geschlossene Form T (n) = O(nlog2 7). Bemerkungen: log2 7 2:81. Coppersmith und Winograd haben den Exponenten 1986 auf 2.38 verbessern konnen. Es ist ein oenes Problem, wie nahe die opere Schranke der magischen n2 Grenze angenahert werden kann. Aufgabe 17: Countingsort ist ein Sortierverfahren ohne Vergleiche. Gegeben ist ein Array A[0..n-1] mit A[i] 2 f0; :::; k 1g. Ausgabe ist ein sortiertes Array B[0..n-1]. Genutzt wird ein Array C[0..k-1], in dem die Anzahlen der Elemente der Groe A[i] innerhalb gezahlt werden. Diese Information wird dann zur Extraktion der sortierten Reihenfolge genutzt. Schreiben Sie ein Pseudocodeprogramm fur Countingsort und zeigen Sie, da die Laufzeit in O(n + k) liegt. Losung zur Aufgabe 17: CountingSort(int A[], int B[]) { int C[] = new int[k]; for (int i=0;i<k;i++) C[i] = 0; for (int i=0;i<n;i++) C[A[i]]++; for (int i=0;i<k;i++) C[i] = C[i]+C[i-1]; for (int i=n-1;i>=0;i--) { 203 B[C[A[i]]-1] = A[i]; C[A[i]]--; } } Die Laufzeit ist durch die Schleifenintervalle festgelegt. Aufgabe 18: p Nach der Stirling-Formel konvergiert der Quotient von n! und 2nn+1=2e n : Zeigen Sie log n! n log n 1:4427n + O(log n): Losung zur Aufgabe 18: (a) log n! n log n n log e + O(log n) n log n 1:4427n + O(log n) Aufgabe 19: Sortieren Sie die Elemente 4,7,8,9,0,1,5,3,2,6 (a) mittels Quicksort (nutzen Sie dabei die erste Zahl als Pivotelement). (b) mittels Heapsort. Zur Aufgabenlosung zugelasse Arbeitsmaterialien sind nur Zettel und Stift. Losung zur Aufgabe 19: (a) Keine Losung (b) Aufbauphase: (4,7,8,9,0|1,5,3,2,6) { Vergleich (6,0) (4,7,8,9|0,1,5,3,2,6) { Vergleich (9,3,2) (4,7,8|2,0,1,5,3,9,6) { Vergleich (1,5,8) (4,7|1,2,0,8,5,3,9,6) { Vergleich (7,2,0),(6,7) (0,2,1,3,6,8,5,4,9,7) { Vergleich (4,0,1),(4,2,6),(4,3,9) Sortierphase: (7,2,1,3,6,8,5,4,9|0) { Vergleich (7,1,2),(7,8,5) (9,2,5,3,6,8,7,4|1,0) { Vergleich (9,2,5),(9,3,6),(9,4) (9,3,5,4,6,8,7|2,1,0) { Vergleich (9,3,5),(9,4,6) (7,4,5,9,6,8|3,2,1,0) { Vergleich (7,4,5),(7,9,6) (8,6,5,9,7|4,3,2,1,0) { usw. KAPITEL 10. THEORIE 204 Aufgabe 20: Sei S eine Menge von n paarweise verschiedenen Elementen. Ein -Goodsplitter liefert fur 2 [1=2; 1) (unabhangig von S ) ein Element x in der Menge S mit jfy 2 Sj y < xgj (1 )n und jfy 2 Sj y > xgj (1 )n. In der Vorlesung haben Sie gesehen, da der Median-der-Mediane ein deterministischer -Goodsplitter mit 7=10 ist. Wir stellen nun einen randomisierten -Goodsplitter vor: Goodsplitter (S ) repeat Wahle x 2 S zufallig until x ist ein -Goodsplitter Zu zeigen ist, da Goodsplitter im Mittel 1=(2 1) Versuche benotigt, um ein gutes Splitelement I zu nden. Die Wahrscheinlichkeit p(I = i), da man dazu i Versuche benotigt, ist [2(1 )]i 1 (1 2(1 )). (a) Erklaren Sie, wie sich dieser Wert zusammensetzt. (b) Berechnen Sie den Erwartungswert E [I ] = Pi1(i p(I = i)) fur die Anzahl der Versuche. Es ist dabei nutzlich, = 2(1 ) zu setzen und sich der Berechung von Pi1 i=2i zu erinnern (HEAPSORT-Generierungsphase). (c) Folgern Sie: Goodsplitter (S ) benotigt eine erwartete Zahl von hochstens n=(2 1) Vergleichen. Losung zur Aufgabe 20: (a) Urnenexperiment: Anteil Kugeln Mierfolg 1 Mierfolgen im i-ten Schritt Erfolg. (b) Erfolg. Nach i 1 Runden mit E[I ] = X i p(I = i) i X = (1 ) ii i X X i = (1 ) i j<i XX i = (1 ) 1 1 1 10 1 j 0 i>j 1 X X i 1+j j 0 i>0 X X = (1 ) j i 1 205 = (1 ) j 0 = (1 ) X i1 j (1 ) X j j0 = j 0 = 1 1 (c) Eine Runde kosten gema Aufteilungsschritt in Divide maximal n Vergleiche. Demnach ist nE [I ] die erwartete Anzahl von Vergleichen. Aufgabe 21: Select (S; k) liefere die k-te Zahl aus der in der aufsteigender Ordnung sortierten Menge S mit 1 k n. Select (S; k) x Goodsplitter (S) fy 2 Sj y < xg fy 2 Sj y > xg cases jS<j k : return Select (S<; k) jS<j = k 1 : return x jS<j < k 1 : return Select (S>; k (jS<j + 1)) S< S> Bermerkung: Bei diesem Algorithmus (und dem in der nachsten Aufgabe) fehlt die Abbruchbedingung fur die Rekursion. Zeigen Sie, da falls Goodsplitter hochstens en Vergleiche braucht (e eine Konstante), dann braucht Select hochstens (e + 1) n=(1 ) viele Vergleiche. Hinweis: Rollen Sie die Rekursionsgleichung S (n) en + n + S (n) auf. Losung zur Aufgabe 21: S(n) = en + n + S(n) = (e + 1)n + S (n) = (e + 1)n + (e + 1)n + S (2n) KAPITEL 10. THEORIE 206 = (e + 1)n(0 + 1 + 2 + : : :) = (e + 1)n=(1 ) Aufgabe 22: Die folgende Prozedur Splitsort nutzt die Bestimmung des Medians durch den Select Aufruf Select (S; dn=2e) aus Aufgabe 4. Splitsort (S ) x Select (S; dn=2e) fy 2 Sj y < xg fy 2 Sj y > xg return Splitsort (S<; x; S> ) S< S> Zeigen Sie: Falls Select bei der Eingabegroe n hochstens dn Vergleiche braucht (d konstant), dann braucht Splitsort hochstens O(n log n) Vergleiche. Zusatzaufgabe: (ohne Bewertung) Zeigen Sie, da fur n = 2k ist die Anzahl der Vergleiche in Splitsort genauer durch (d + 1) n (1 + log n) beschrankt ist. Folgern Sie, da Splitsort mit dem randomisierten Goodsplitter aus Aufgabe 3 im Mittel maximal 5 22 1 n (1 + log n) viele Vergleiche benotigt. (1 )(2 2) Losung zur Aufgabe 22: Die Rekursionsgleichung ist von der Form T (n) 2T (n=2) + cn mit einem konstanten Aufwand fur T (1) von sagen wir c. Fur n = 2k ist T (n) = c 2k (k + 1) = c n (1 + log n). Dies lat sich leicht durch Induktion zeigen: T (2k) = 2T (2k 1) + c 2k = 2 c 2k 1 k + c 2k = c 2k (k + 1). In unserem Fall ist c = d + 1, so da (d + 1) n (1 + log n) = O(n log n) Vergleiche in Splitsort notwendig sind. Die Folgerung entsteht durch Einsetzen von den zwei vorangegangenen Aufgaben. Aufgabe 23: Losen Sie mithilfe des Master-Theorems die folgenden drei Rekursionsgleichungen auf und bestimmen Sie die Groenordnung von T (n): (a) T (n) = 8T (n=3) + n2 (b) T (n) = 9T (n=3) + n2 (c) T (n) = 10T (n=3) + n2 207 Dabei konnen Sie von den Anfangswerten und der Nicht-Ganzzahligkeit abstrahieren. Losung zur Aufgabe 23: (a) T (n) = (n2) (b) T (n) = (n2 log n) (c) T (n) = (nlog3 10) Aufgabe 24: Finden Sie fur die folgenden Rekursionsgleichungen die Groenordnungen fur T (n): (a) T (n) = 2T ( pn) + log n. (b) T (n) = n + T (n=4) + T (3n=4). Abstrahieren Sie von den Anfangswerten und der Ganzzahligkeit der Argumente in T . Losung zur Aufgabe 24: Uwe Schoning: Algorithmen, kurz gefat. Seite 25-27. Aufgabe 25: Sei T (r) ein geordneter Baum (die Kinder eines Knotens sind linear geordnet) mit Wurzel r und Kindern v ; : : : ; vn. Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v )) : : : post(T (vk)) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v )) r in(T (v )) : : : in(T (vk )), der Preorderdurchlauf pre(T ) als r pre(T (v )) : : : pre(T (vk )) und der Levelorderdurchlauf lev(T ) als r lev(T (v )) r lev(T (v )) : : : r lev(T (vk)) r festgelegt. Beweisen Sie die folgenden Aussagen fur paarweise verschiedene Schlussel in T oder nden 1 1 1 2 1 1 2 Sie ein Gegenbeispiel. (a) Aus der Levelorder kann T eindeutig rekonstruiert werden. (b) Preorder und Inorder beschreiben einen Baum T eindeutig. Unterscheiden Sie dabei den binaren (max. zwei Kinder) und den allgemeinen Fall. (c) Preorder und Postorder beschreiben T eindeutig. Losung zur Aufgabe 25: KAPITEL 10. THEORIE 208 (a) Ja. Besteht lev(T ) nur aus einem Knoten, hat der Baum nur diesen Knoten. Ansonsten ist der erste Knoten Wurzel des Baumes. Zwischen den verschiedenen Darstellungen der Wurzel nden wir fur die Teilbaume die Levelorder. Nach Induktionsvoraussetzung konnen wir die Teilbaume eindeutig rekonstruieren. (b) Nein. Gegenbeispiel, ex. 2 Baume T1; T2 mit pre(T1) = pre(T2) = 1; 2; 3; 4; 5; 6 in(T1) = in(T2) = 2; 1; 4; 3; 5; 6: lev(T1) = 1; 2; 1; 3; 4; 3; 5; 3; 1; 6; 1 und lev(T2) = 1; 2; 1; 3; 4; 3; 1; 5; 1; 6; 1 Fur Binarbaume gilt die Aussage. Beweis: Nach Denition gilt pre(T ) = r pre(T ) : : : pre(Tk) 1 und in(T ) = in(T ) r in(T ) : : : in(Tk ): 1 2 Aus pre(T ) entnehmen wir, da der erste Knoten Wurzel von T ist. Wir suchen in in(T ) und erhalten links und rechts von r in(T1) und in(T2). Damit kennen wir auch die Knotenmengen von T1 und T2 und konnen pre(T1) und pre(T2) trennen. Mit den Darstellungen fur T1 und T2 fahren wir analog fort, bis wir auf Baume mit einem Knoten stoen. Anmerkung: Aufgrund der Symmetrie von Preorder und Postorder konnen wir in dem Beweis Preorder durch Postorder ersetzen. (c) Ja. Nach Denition gilt: pre(T ) = r pre(T ) : : : pre(Tk) 1 und post(T ) = post(T ) : : : post(Tk) r; 1 wobei k a priori nicht bekannt ist. Oensichtlich konnen wir die Wurzel r erkennen. Der erste Knoten in pre(T1) ist die Wurzel von T1, die in post(T1) am Ende steht. Damit konnen wir post(T1) abtrennen. Da wir nun die Knotenmenge von T1 kennen konnen wir auch pre(T1) abtrennen. Dieses Verfahren fuhren wir fort, bis wir k, pre(T1), pre(T2), . . . , pre(Tk ), post(T1), post(T2), . . . , post(Tk ) kennen. Fur diese Teilbaume fahren wir analog fort, bis wir auf Baume mit einem Knoten stoen. Aufgabe 26: Wir analysieren das MIN-MAX-Problem aus dem letzten Praxisblatt. Innerhalb eines zufallig mit den n Ganzzahlelementen a[0]; : : : ; a[n 1] gefullten Arrays a soll das minimale und maximale Element gefunden werden. Sie konnen n als gerade voraussetzen. 209 (a) Zeigen Sie, da n 1 Vergleiche notwendig sind, um eines der beiden Elemente zu nden und ein solches Verfahren existiert. (b) Zeigen sie, da ein einfaches Scan-Verfahren mit den Variablen maxsofar existiert, das h ochstens 3n=2 2 Vergleiche benotigt. minsofar und (c) Zeigen Sie, da mindestens 3n=2 2 Vergleiche zur Losung des Problems notwendig sind. Dabei beginnen Sie wie folgt: \Sei ein korrektes Verfahren V zur Losung des MIN-MAX-Problems gegeben. Seien SMIN (bzw. SMAX ) die Mengen derjenigen Elemente, die in einem Zustand von V sicher groer (kleiner) sind als das Minimum (Maximum) aller. Initial ist S =j SMIN j + j SMAX j= 0 und zum Ende von V ist S = 2n 2. Bezeichne mit comp(a; b) = (minfa; bg; maxfa; bg) die Vergleichsoperation zweier Werte a; b. Eine Anwendung von comp(a; b) kann die Mengen SMIN und SMAX vergroern." Geben Sie eine Fallunterscheidung der Vergleichsmoglichkeiten an, und der Gewinn an Information, der sich als Summand von S ergibt. Losung zur Aufgabe 26: (a) Verfahren: Vergleich a[1] und a[2]. Vergleich maxfa[1]; a[2]g und a[3] usw. Dieses sind n 1 Vergleiche. Da bei jedem Vergleich, den ein Algorithmus tatigt, hochstens ein Element als Nicht-Maximum ausscheiden kann, mussen mindestens n 1 Vergleiche durchgefuhrt werden. (b) Man betrachte im Scanverfahren immer zwei Arrayelemente auf einmal und vergleiche sie. Das resultierende Minimum wird mit minsofar verglichen und das resultierende Maximum mit maxsofar. Dieses fuhrt zu n=2 + (n=2 1) + (n=2 1) = 3n=2 vielen Vergleichen. (c) Fur S ergibt sich ein Summand von 0 (ungunstig) oder 2 (gunstig), falls (a; b) 2 SMIN SMAX [SMAX SMIN , 2, falls a; b 2= SMIN [ SMAX , 1, falls (a; b) 2 SMIN SMIN [ SMAX SMAX , 1 (ungunstig) oder 2 (gunstig), sonst. KAPITEL 10. THEORIE 210 Es geht um Mindestanzahl der Vergleiche des besten Algorithmus bei der ungunstigsten Eingabe. Der Algorithmus mit den wenigsten Vergleichen wahlt dementsprechend die Falle aus (soweit moeglich) und die schlechteste Eingabe beschert ihn dann den ungunstigsten Fall. Wenig Schritte entsprechen viel Information, deshalb waehlt der beste aller Algorithmen den Fall mit den garantierten 2 Einheiten soweit moeglich. Dies kann er aber nur n=2 mal. Dann waehlt er einen Fall, der ihm mindestens eine Informationseinheit liefert. Dies macht er, bis der Rest der 2n 2 2n=2 Informationseinheiten verbraucht sind. Demnach werden mindestens n=2+(2n 2 n) = 3n=2 2 viele Vergleiche im worst case durchgefuhrt. Aufgabe 27: Fugen Sie der Reihe nach die folgenden Schlussel in einen lexikographisch geordneten, zunachst leeren naturlichen Suchbaum ein: eins, zwei, . . . , zehn. Danach loschen Sie die Schlussel sieben, zwei und vier. Losung zur Aufgabe 27: Keine Losung. Aufgabe 28: Zeichnen Sie fur die Schlusselmenge S = f1; 2; 3g alle Suchbaume der Hohe 3. Losung zur Aufgabe 28: 3i 3i 1i 1i L L L L 2i 3i 2i 1i L L L L 1i 2i 2i 3i L L L L Aufgabe 29: Sei N = 2h 1 mit h als naturliche Zahl. Sei T (h) die Anzahl der Permutationen einer N -elementigen Schlusselmenge, die beim Einfugen in den anfangs leeren Baum einen vollstandigen Suchbaum erzeugen. Geben Sie ein Rekursionsgleichungssystem zur Bestimmung von T (h) an. Berechnen Sie danach T (h) fur h 2 f0; 1; 2; 3g. Losung zur Aufgabe 29: Es ist 8 < h=0 T (h) = : T (h 1)12 2h 2 falls sonst. 2h 1 1 211 Der Fall h = 0, N = 0 ist wieder einfach, so wie oben. Ansonsten (h 1, N = 2h 1) mu das mittlere Element der Schlusselmenge zuerst in die Wurzel eingefugt werden. Fur den linken und den rechten Teilbaum stehen dann unabhangig voneinander T (h 1) viele Moglichkeiten zur Verfugung. Auerdem konnen noch 2h 1 1 viele Positionen in der Sequenz von 2h 2 weiteren einzuf ugenden Schlusseln fur die Schlussel des linken 2h 2 Teilbaums bestimmt werden, das macht 2h 1 1 viele Moglichkeiten. Es ist h 0 1 2 3 4 ::: T (h) 1 1 2 80 21964800 : : : Aufgabe 30: Wieviele verschiedene Strukturen S (N ) von Suchbaumen fur N Schlussel der Hohe N gibt es? Geben Sie eine kurze Begrundung an! 8 < Losung zur Aufgabe 30: S (N ) = : N1 2 falls N=0 1 sonst. Im Fall N = 0 gibt es eine Struktur, der Baum besteht aus einem Blatt, das auch Wurzel ist. Ansonsten kann jeder innere Knoten auer der Wurzel entweder linker oder rechter Sohn seines Vaters sein, was jeweils 2 Moglichkeiten ergibt, nur fur die Wurzel gibt es nur eine Moglichkeit. Aufgabe 31: Sei T (r) ein Baum mit Wurzel r und Kindern v1; : : : ; vn . Dann ist der Postorderdurchlauf post(T ) deniert als post(T (v1)) : : : post(T (vk )) r. Entsprechend sind der Inorderdurchlauf in(T ) als in(T (v1)) r in(T (v2)) : : : in(T (vk )), der Preorderdurchlauf pre(T ) als r pre(T (v1)) : : : pre(T (vk)) und der Levelorderdurchlauf lev(T ) als r lev(T (v1)) r lev(T (v2)) : : : r lev(T (vk)) r festgelegt. Beweisen Sie die folgenden Aussagen fur paarweise verschiedene Schlussel in T oder nden Sie ein Gegenbeispiel. (a) Aus der Levelorder kann T eindeutig rekonstruiert werden. (b) Preorder und Inorder beschreiben einen Baum T eindeutig. Unterscheiden Sie dabei den binaren (max. zwei Kinder) und den allgemeinen Fall. (c) Preorder und Postorder beschreiben T eindeutig. Losung zur Aufgabe 31: KAPITEL 10. THEORIE 212 (a) Ja. Besteht lev(T ) nur aus einem Knoten, hat der Baum nur diesen Knoten. Ansonsten ist der erste Knoten Wurzel des Baumes. Zwischen den verschiedenen Darstellungen der Wurzel nden wir fur die Teilbaume die Levelorder. Nach Induktionsvoraussetzung konnen wir die Teilbaume eindeutig rekonstruieren. (b) Nein. Gegenbeispiel, ex. 2 Baume T1; T2 mit pre(T1) = pre(T2) = 1; 2; 3; 4; 5; 6 in(T1) = in(T2) = 2; 1; 4; 3; 5; 6: lev(T1) = 1; 2; 1; 3; 4; 3; 5; 3; 1; 6; 1 und lev(T2) = 1; 2; 1; 3; 4; 3; 1; 5; 1; 6; 1 Fur Binarbaume gilt die Aussage. Beweis: Nach Denition gilt pre(T ) = r pre(T ) : : : pre(Tk) 1 und in(T ) = in(T ) r in(T ) : : : in(Tk ): 1 2 Aus pre(T ) entnehmen wir, da der erste Knoten Wurzel von T ist. Wir suchen in in(T ) und erhalten links und rechts von r in(T1) und in(T2). Damit kennen wir auch die Knotenmengen von T1 und T2 und konnen pre(T1) und pre(T2) trennen. Mit den Darstellungen fur T1 und T2 fahren wir analog fort, bis wir auf Baume mit einem Knoten stoen. Anmerkung: Aufgrund der Symmetrie von Preorder und Postorder konnen wir in dem Beweis Preorder durch Postorder ersetzen. (c) Ja. Nach Denition gilt: pre(T ) = r pre(T ) : : : pre(Tk) 1 und post(T ) = post(T ) : : : post(Tk) r; 1 wobei k a priori nicht bekannt ist. Oensichtlich konnen wir die Wurzel r erkennen. Der erste Knoten in pre(T1) ist die Wurzel von T1, die in post(T1) am Ende steht. Damit konnen wir post(T1) abtrennen. Da wir nun die Knotenmenge von T1 kennen konnen wir auch pre(T1) abtrennen. Dieses Verfahren fuhren wir fort, bis wir k, pre(T1), pre(T2), . . . , pre(Tk ), post(T1), post(T2), . . . , post(Tk ) kennen. Fur diese Teilbaume fahren wir analog fort, bis wir auf Baume mit einem Knoten stoen. Aufgabe 32: Fugen Sie die Monatsnamen nacheinander in einen lexikographisch geordneten, anfangs 213 leeren AVL-Baum ein. Wenn immer eine Rebalancierung notwendig ist, zeichnen Sie den Baum. Losung zur Aufgabe 32: Hartmund Guting Datenstrukturen und Algorithmen, Teubner. Seite 126-127. Aufgabe 33: Betrachten Sie die Struktur eines AVL-Baumes aus Abbildung 1. Abbildung 10.2: Die Struktur eines AVL-Baumes mit 10 Schlusseln. (a) Geben Sie die verschiedenen Strukturen der AVL-Baume an, die durch die Einfugung von einem Schlussel entstehen konnen. (b) Vorausgesetzt, da die Schlussel mit der gleichen Wahrscheinlichkeit in jedes der Intervalle zwischen den bereits im Baum bendlichen Schlusseln fallen; wie gro ist die Wahrscheinlichkeit, da die Hohe des angegebenen AVL-Baumes nach zwei Einfugungen um eins wachst? Losung zur Aufgabe 33: 214 KAPITEL 10. THEORIE Erste Einfugung: Situation 1,2 und 5 in 3/11 der Falle, Situation 3,4 in 1/11 der Falle. Zweite Einfugung: Nur in den Fallen 1 und 2 kann die Hohe des Baumes wachsen, jeweils mit Wahrscheinlichkeit 4/12. Damit ergibt sich insgesamt eine Wahrscheinlichkeit von 2(3/11 4/12) = 2/11. Aufgabe 34: (Knotengefarbte) Rotschwarz-Baume sind binare Suchbaume mit den folgenden Eigenschaften: i) Jeder Knoten ist entweder rot oder schwarz. ii) Jedes Blatt ist schwarz. iii) Falls ein Knoten rot ist, dann sind beide Kinder schwarz. iv) Jeder Pfad von einem Knoten zu einem Blatt enthalt die gleiche Anzahl von schwarzen Knoten. Zeigen Sie 215 (a) Ein (knotengefarbter) Rotschwarzbaum mit von hochstens 2 log(n + 1). n inneren Knoten besitzt eine Hohe (b) Entsprechend den AVL-Baumen gibt es Operationen Left-Rotate und Right-Rotate. Skizzieren Sie die Einf arbungs- und Balancierungsoperationen der Einfugeprozedur eines Elementes in einem Rotschwarzbaum in Pseudocode. Dabei starten Sie mit dem eingefugten Knoten und der Farbung \rot" und hangeln sich je nach Lage und Farbe der Ahnen mittels Rotationen und Umfarbungen uber die Vorgangerverweise an den Knoten zu den Eltern und Ureltern hinauf. Losung zur Aufgabe 34: Cormen, Leiserson Rivest Introduction to Algorithms Seite 263-266. Aufgabe 35: Persistente Datenstrukturen ermoglichen einen Zugri in die Vergangenheit der dynamischen Veranderungen. Wir konzentrieren uns auf persistente Suchbaume. Eine Moglichkeit zur Implementation ist es, eine Kopie des Suchbaumes anzulegen, wenn immer er sich verandert. 4 3 2 4 4 3 8 7 10 2 8 7 7 8 10 5 Abbildung 10.3: Ein persistenter Suchbaum vor und nach dem Einfugen von 5. Eine ezientere Moglichkeit (vergleiche Abbildung 2) besteht darin, die sich nicht verandernden Teile der Datenstruktur wiederzuverwerten. (a) Beschreiben Sie die Knoten, die bei der Einfugung eines Schlussels k oder bei der Loschung eines Knotens y verandert werden mussen. (b) Angenommen, die derzeitige Hohe des Suchbaumes ware durch h gegeben und Knoteninformation bestunde aus dem Schlussel, dem linken und dem rechten Nachfolger. Was ist die Laufzeit des Einfugens eines Knotens in die persistente Suchbaumstruktur? KAPITEL 10. THEORIE 216 (c) Geben Sie Argumente dafur an, da sich der Algorithmus bei gleicher Laufzeit auf balancierte Suchbaume erweitern lat. Losung zur Aufgabe 35: Kurzlosung: (a) Es sind die Knoten auf dem Suchpfad der sich durch die Einfugung des Knotens ergibt. Beim Loschen mu der Pfad zum symmetrischen Vorganger bzw. Nachfolger berucksichtigt werden. (b) Der Algorithmus lauft in O(h). (c) Der Algo lauft dann in O(log n), da auch die Rotationsoperationen lokal entlang eines Pfades agieren. Ausfuhrliche Informationen uber Persistente Datenstrukturen, insbesondere, wie sich eine Schranke von O(1) fur den zusatzlichem Platz nden lat, siehe Discroll, Sarnak, Sleator and Tarjan, Making Data Structures Persistent, Journal of Computer and System Sciences 38, 86-124 (1989). Aufgabe 36: Fugen Sie der Reihe nach die folgenden Schlussel in einen zunachst leeren (a) AVL-Baum (b) Bruder-Baum (c) (2-3)-Baum (B-Baum der Ordnung 3) ein: 4,5,7,2,1,3,6. Danach loschen Sie die Schlussel 5,6 und 7. Geben Sie die Zwischenergebnisse und die Art der durchgefuhrten Operationen an. Losung zur Aufgabe 36: Keine Losung. Aufgabe 37: Seien a; b; c naturliche Zahlen und n 2 fc0; c1; c2; : : :g. 8 < b; fur n = 1 (a) Falls T (n) = : aT (n=c) + bn; fur n > 1 so folgt 8 > falls a < c > < O(n); T (n) = > O(n log n); falls a = c > : O(nlogc a); falls a > c 217 8 < b; fur n = 1 (b) Falls T (n) = : aT (n=c) + bn2; fur n > 1 so folgt 8 2 > falls a < c2 > < O(n2); T (n) = > O(n log n); falls a = c2 > : O(nlogc a); falls a > c2 Hinweis: Setzen Sie r = a=c bzw.Pr = a=c2 und nutzen Sie, die Darstellung T (n) = P n log n bn log i=0c ri bzw. T (n) = bn2 i=0c ri. Diese Darstellungen kann man jeweils durch eine einfache Induktion bestatigen. Unterscheiden Sie die Falle r < 1, r = 1 und r > 1. Losung zur Aufgabe 37: Wir beweisen nur die zweite Aussage. Die erste Aussage cn i verlauft analog. Vorerst zeigen wir per Induktion, da T (n) = bn2 Plog r ist. Fur i=0 n = 1 gilt T (n) = b. Fur n > 1 ist T (n) = aT (n=c) + bn2 (n=c) 2 logcX = a b nc2 ri + bn2 i=0 Xn = a b nc2 2 logc ( ) 1 = bn2(1 + r = = = Es gibt 3 Falle. i=0 Xn ri + bn logc ( ) 1 2 ri ) i=0 n bn2(1 + X ri+1 r1+logc n) i=0 logc n bn2(1 + X ri + r1+logc n r1+logc n) i=1 logc n X bn2 ri i=0 logc Fall 1: a < c2 also r < 1. Dann gilt T (n) bn2 + X ri = bn2 1 1 r = O(n2) i0 KAPITEL 10. THEORIE 218 Fall 2: a = c2 also r = 1. Dann gilt T (n) = bn2(1 + logc n) = O(n2 log n) Fall 3: a > c2 also r > 1. Dann gilt T (n) = bn + r rr 1 1 = = O(n r n) = O((c ) = O(a n) = O(n a) logc 2 2 logc logc Aufgabe 38: n 2 logc logc Zeigen Sie: Ein k-narer Baum (jeder innere Knoten hat insgesamt (Blatter und innere Knoten) r + kr 11 Knoten. n (a=c2 )logc n ) k Sohne) mit r Blattern hat Losung zur Aufgabe 38: Induktion uber die Anzahl der Blatter r. Ein k-narer Baum mit k Blattern besitzt k + (k 1)=(k 1) = k + 1 Knoten. Ein k-narer Baum mit r Blattern besteht aus einer Wurzel r, einem Teilbaum mit r1 Blattern einem Teilbaum r2 Blattern und einem Teilbaum mit rk Blattern. Die Gesamtzahl der Knoten ist demnach 1 + r1 + rk1 11 + r2 + rk2 11 + : : : + rk + rkk 11 = 1 + r + rk k1 = r + kr 11 Aufgabe 39: Fugen Sie die Schlusselfolge 16,44,21,5,19,22,8,33,27,30 gema der Hashfunktion h(k) = k mod m fur m = 11 in eine Hashtabelle mittels oener Adressierung ein. Als Sondierungsfolge nutzen Sie die Funktion h(k) s(j; k) mod m. Berechnen Sie dabei den Quotient aus der Anzahl von Vergleichen fur die Suche der einzelnen Elemente in der endgultigen Tabelle mit der Anzahl der insgesamt gespeicherten Elemente. (a) Verwenden Sie lineares Sondieren s(j; k) = c j mit c = 1. (b) Verwenden Sie die quadratische Sondierungsfolge s(j; k) = ( 1)j dj=2e2. (c) Verwenden Sie Doppel-Hashing s(j; k) = h0(k) j . Bestimmen Sie ein geeignetes h0 (Die Eignung von h0 mu nicht exakt bewiesen werden). (d) Geben sie fur den Aufgabenteil (a) und m = 14 moglichst allgemein alle c an, die sich als unbrauchbar erweisen. Begrunden Sie Ihre Losung. 219 Losung zur Aufgabe 39: Kollisionen gibt es fur die Gruppen 16,5,27 { 44,22,33 und 19,8,30. Damit ergibt sich (a) Lineares Sondieren: Mittlerer Zugri 2.5 Belegung: 44,-,30,27,5,16,33,8,19,22,21. (b) Quadratisches Sondieren: ,27,33,16,5,30,19,8,21. Mittlerer Zugri: 2.2 Belegung: (c) Doppel-Hashing: Eine mogliche Hashfunktion: s(j; k) = j ((4 mod k) lerer Zugri: 1.9. Belegung: 44,30,8,22,33,16,-,27,19,5,21. 44,22,- 5) Mitt- (d) Alle c mit ggt(c; m) = 6 1 sind schlecht, da die Tabelle nur zu einem Bruchteil ausgelastet wird. Aufgabe 40: Sortieren Sie die Elemente 4,7,8,9,0,1,5,3,2,6 (a) mittels Quicksort (nutzen Sie dabei die erste Zahl als Pivotelement). (b) mittels Heapsort. Zur Aufgabenlosung zugelasse Arbeitsmaterialien sind nur Zettel und Stift. Losung zur Aufgabe 40: (a) (4,7,8,9,0,1,5,3,2,6) (0,1,3,2)(4)(7,8,9,5,6) (0)(1,3,2)(4)(7,8,9,5,6) (0)(1)(3,2)(4)(7,8,9,5,6) (0)(1)(2)(3)(4)(7,8,9,5,6) (0)(1)(2)(3)(4)(5,6)(7)(8,9) (0)(1)(2)(3)(4)(5)(6)(7)(8,9) (0)(1)(2)(3)(4)(5)(6)(7)(8)(9) (b) Aufbauphase: (4,7,8,9,0|1,5,3,2,6) { Vergleich (6,0) (4,7,8,9|0,1,5,3,2,6) { Vergleich (9,3,2) (4,7,8|2,0,1,5,3,9,6) { Vergleich (1,5,8) (4,7|1,2,0,8,5,3,9,6) { Vergleich (7,2,0),(6,7) (0,2,1,3,6,8,5,4,9,7) { Vergleich (4,0,1),(4,2,6),(4,3,9) KAPITEL 10. THEORIE 220 Sortierphase: (7,2,1,3,6,8,5,4,9|0) { Vergleich (7,1,2),(7,8,5) (9,2,5,3,6,8,7,4|1,0) { Vergleich (9,2,5),(9,3,6),(9,4) (9,3,5,4,6,8,7|2,1,0) { Vergleich (9,3,5),(9,4,6) (7,4,5,9,6,8|3,2,1,0) { Vergleich (7,4,5),(7,9,6) (8,6,5,9,7|4,3,2,1,0) { usw. Aufgabe 41: Auswahlsort ist ein einfacher Vorlaufer von Heapsort. Gegeben sei ein Elementarray A von n vergleichbaren Objekten. Der Algorithmus sucht fur j = 1; : : : ; n 1 in dem Array von der Stelle j aus das kleinste Element und tauscht es an die Stelle j . (a) Bestimmen Sie die Anzahl der Vergleiche im best, worst und average case. (b) Besimmen Sie die Anzahl von Bewegungen im best, worst und average case. Losung zur Aufgabe 41: Vergleiche Ottmann und Widmayer Algorithmen und Datenstrukturen Seite 66 . (a) Cmin(n) = Cmax(n) = Cmit(n) = Pni=11(n i) = Pni=11 i = (n2) (b) Cmin(n) = Cmax(n) = Cmit(n) = 3(n 1) Aufgabe 42: Heaps sind einfache Datenstrukturen mit einem hohen Freiheitsgrad bezuglich der Anordnung der Elemente. (a) Zeigen Sie, da und wie der Pfad zur Stelle n in einem Heap durch die Binardarstellung von n bestimmt ist. (b) Heaps lassen sich als Vorrangwarteschlangen nutzen. U berlegen Sie, wie man die Datenstruktur anreichern kann, um eine beidseitige Vorrangwarteschlange zu erhalten, die sowohl die Extraktion des Minimum als auch des Maximums in logarithmischer Zeit erlaubt. (c) Die harte Nu (freiwillige Zusatzaufgabe): Sei f (n) die Anzahl der Heaps mit n paarweise verschiedenen Schlusseln und si die QGoe des Teilbaumes zur Wurzel n i, 1 i n. Zeigen n 1Sie, da f (n) = n!= i=1 si gilt. Nutzen Sie dabei die Rekursion f (n) = jT1j f (jT1j)f (jT2j) mit T1 bzw. T2 als der rechte bzw. linke Teilbaum der Wurzel. 221 Losung zur Aufgabe 42: (a) Der Pfad P zur Stelle n = (bk : : : b0) mit k = dlog ne besitzt die Wurzelelemente (1 bk 1 : : : bi), i 2 f0; : : : ; kg: (b) Die Idee ist es, eine minimum-geordnete und eine maximum-geordnete Vorrangwarteschlange M bzw. M 0 zu bilden und die Elementevektoren a bzw. a0 mit einem Querverweis zu verbinden. Genauer: Wir verwalten zwei Bijektionen und 0 , so da ai = a0(i) und a0 (i)=a0i gilt. Da sich jede Datenstrukturoperation auf einen Sequenz von Tauschoperationen zuruckfuhren, lassen sich fur jeden Tausch die Invarianzen (0(j )) = j und 0 ((j )) = j in O(1) Zeit bewahren. Eine alternative Implementation als balancierten Suchbaum schliet die Aufgabe nicht aus. (c) Die Division durch n! liefert f (n) = 1 f (jT j) f (jT j) n! n jT j! jT j! 1 1 2 2 und Expansion der Formel hin zu den Blattern das gewunschte Resultat. Aufgabe 43: Das in der Vorlesung vorgestellte Bottom-Up-Heapsort Verfahren lat durch eine geschickte Organisation der Tauschoperationen in der Anzahl der Schlusselbewegungen noch verbessern. A ndern Sie das in der Vorlesung vorgestellte Java-Programm so ab, da keine Schlusselbewegungen wahrend der Sinkphase oder in der Methode bubbleUp ausgefuhrt werden. Die Schlusselbewegungen sollen in einem dritten Schritt erfolgen und keine weiteren Schlusselvergleiche erfordern. Die Anzahl der Schlusselbewegungen soll proportional zur Tiefe der fur den Schlussel gefundenen Position sein. (Hinweis: Merken Sie sich die Indizes der wahrend der Sinkphase betrachteten Knoten in einem Hilfsarray.) Losung zur Aufgabe 43: PROCEDURE LeafSearch(m,i) j = i WHILE 2j < m DO IF a[2j] < a[2j + 1] THEN j = 2j ELSE j = 2j + 1 IF 2j = m THEN j=m PROCEDURE bubbleUp(i,j) WHILE ( (i < j) AND (a[i] < a[j]) ) DO j = j DIV 2 222 KAPITEL 10. THEORIE PROCEDURE Interchange(i,j) l = bin(j)-bin(i) x = a[i] FOR k = l-1 DOWNTO 0 DO a[j DIV 2^(k+1)] = a[j DIV 2^k] a[j] = x Bemerkung: Bottom{Up{Heapsort benotigt im worst-case 1:5n log n + (n) und im average case n log n + O(n) viele Vergleiche. Aufgabe 44: Jeder kennt die Schwierigkeit, in einer Werkbank zu einer Schraube die passende Mutter zu nden. Wir gehen hierbei von n Schrauben unterschiedlicher Groe und von n korrespondierenden Muttern aus. Der Vergleich der Muttern und der Schrauben untereinander bringt keine neue Information ein. Vergleicht man hingegen eine Schraube mit einer Mutter, so stellt sich heraus, da die Schraube i) entweder pat, ii) zu klein, oder iii) zu gro fur die ausgewahlte Mutter ist. (a) Zeigen Sie, da jeder Algorithmus zur Losung des Problems (n log n) viele Vergleiche benotigt. Orientieren Sie sich hierbei an dem Beweis der unteren Schranke fur allgemeine Sortierverfahren. (b) Geben Sie einen Algorithmus an, der fur die Suche die kleinsten Schraube und ihrer korrespondierenden Mutter 2n 2 Vergleiche benotigt. Losung zur Aufgabe 44: (a) Der Beweis beruht auf einem Entscheidungsbaum, an dem die inneren Knoten entsprechend den Ausgangen des Vergleiches einer Mutter mit einer Schraube den Verzweigungsgrad 3 haben. Die Aufgabe kann so interpretiert werden, da eine Permutation der Zahlen f1; 2; : : : ; ng gesucht wird, so da die (i)-te Mutter auf die i te Schraube pat. Jedem Blatt im Entscheidungsbaum weisen wir nun eine der n! verschiedenen Permutationen zu. Durch die Vergleiche an den Knoten mussen analog zu der Betrachtung in allgemeinen Sortierverfahren alle Permutationen erzeugt bzw. verarbeitet werden konnen. Ein Pfad innerhalb des Baumes entspricht demnach der Ausfuhrung eines Programmes p zur Zuordnung von Schrauben zu Muttern. Da nach der Sterlingformel n! 2n(n=e)n gilt, ist die minimale Tiefe des Entscheidungsbaumes log3(n!) in (n log n) (b) Hier ist ein Scanverfahren zu empfehlen. Wir betrachten zwei Arrays M (fur Muttern) und S (fur Schrauben) und denieren minsofar als Tripel bestehend aus der 223 derzeit kleinsten Mutter oder Schraube, dem somit assoziierten Typus des Minimums und einen Verweis zu dem entsprechenden Partner in dem jeweils anderen Array. Zu Anfang werden minsofar auf (0,Mutter,-) und zwei Zahler i und j auf Null gesetzt. Danach wird das folgende Programmstuck aufgerufen. while (i<n || j<n) if (M[i] < S[j]) minsofar = (i,Mutter,-) else if (S[j] < M[i]) minsofar = (j,Schraube,-) else minsofar = (i,Mutter,j) j++ Es ist einsichtig, da das Verfahren in seinen schlimmsten Fall gerat, falls sowohl die kleinste Schraube als auch die kleinste Mutter sich am Ende des Arrays bendet. Dieser Fall lat sich jedoch ein Schritt vorher abfangen, so da insgesamt nur 2n 2 Vergleiche zur Losung des Problems notwendig sind. Aufgabe 45: Kategorisieren Sie die vorgestellten Sortierverfahren gema der folgenden 7 Kriterien: 1. Das Sortierverfahren sollte allgemein sein. Objekte aus beliebig geordneten Mengen sollten sortiert werden konnen. 2. Die Implementation des Sortierverfahrens soll einfach sein. 3. Das Sortierverfahren soll internes Sortieren ermoglichen. Dabei steht neben den Arrayplatzen nur sehr wenig Platz zur Verfugung. 4. Die durchschnittliche Zahl von wesentlichen Vergleichen, d.h. Vergleichen zwischen zu sortierenden Objekten soll klein sein. Sie soll fur ein moglichst kleines c durch n log n + cn beschrankt sein. 5. Die worst-case Zahl von wesentlichen Vergleichen soll klein sein. Sie soll fur ein moglichst kleines c0 durch n log n + c0n beschrankt sein. 6. Die Zahl der ubrigen Operationen wie Vertauschungen, Zuweisungen und unwesentliche Vergleiche soll hochstens um ein konstanten Faktor groer als die Zahl der wesentlichen Vergleiche sein. 7. Die CPU-Zeit fur jede ubrige Operation soll klein gegenuber der CPU-Zeit fur einen Vergleich zweier komplexer Objekte sein. Losung zur Aufgabe 45: (mit historischen Anmerkungen) BUCKETSORT basiert auf der Darstellung der zu sortierenden Elemente und ist somit kein allgemeines Verfahren (Kriterium 1). MERGESORT ist ein Divide-and-Conquer Algorithmus und benotigt fur n = 2k durch ein paralleles Durchlaufen der schon sortierten Teillisten nur n log n n + 1 Vergleiche, allerdings ein Array der Lange 2n. Da n recht gro sein kann, ist man an 'InSitu' Algorithmen (Kriterium 3) interessiert. Daher ist MERGESORT nur zum externen Sortieren geeignet. KAPITEL 10. THEORIE 224 INSERTIONSORT (Steinhaus (1958)) ist eines der einfachsten Sortierverfahren. Es benotigt durch n-maliges Einfugen mittels binarer Suche weniger als nX1 i=1 n n i=2 i=2 dlog(i + 1)e = X dlog(i)e X log(i + 1) = log(n!) + n 1 Vergleiche, aber die Anzahl der Vertauschungen liegt selbst im average-case in (n2). Das Interesse gilt aber Sortieralgorithmen, deren Anzahl von Vertauschungen und anderen nicht-essentiellen Operationen klein ist (Kriterium 6). SHELLSORT (Shell (1959)) besitzt als weiteren Parameter eine Folge von naturlichen Zahlen h1; : : : ; ht. Dabei wird sukzessiv INSERTIONSORT auf den Elementen aufgerufen, die einen Abstand hi mit i = t; : : : ; 1 zueinander besitzen. Die geeignete Auswahl der Abstandsfolge ist sehr entscheidend fur das Laufzeitverhalten von SHELLSORT. Es werden im folgenden die Ergebnisse fur den worst-case von Operationen (Vergleiche und Vertauschungen) zusammengefat. Shell (1959) schlug die Abstandsfolge (1; 2; : : : ; 2k ) vor, doch liefert sie im Falle n = 2k eine quadratische Laufzeit. Eine von Hibbard (1963) vorgeschlagene Sequenz ergab in der Analyse von Papernov und Stasevich (1965) O(n3=2). Pratt (1979) gab eine (log2 n) lange Folge an, die zu (n log2 n) vielen Operationen fuhrt. Sedgewick (1986) verbesserte die Grenze von O(n3=2) auch fur O(log n) beschrankte Folgen auf O(n4=3) und in Zusammenarbeit mit Incerpi p (1985) gab er als Weiterentwicklung dieser Idee die obe1+= log n re Grenze von O(n ) fur ein > 0 an. Poonen (1993) zeigte, da dies die bestmogliche Schranke ist. Cypher (1993) bewies fur SHELLSORT Netzwerke und fur monoton fallende Abstandsfolgen eine Mindestgroe von (n log2 n= log log n). Letztendlich fand Poonen (1993) eine untere Grenze von (n(log n= log log n)2) Operationen unabhangig von den gewahlten Abstande auch fur SHELLSORT als allgemeines Sortierverfahren. Die Analysen begrunden sich vielfach auf die Anzahl der Inversionen (vergl. Knuth (1973)) und auf die Betrachtung des Frobenius-Problems (Brauer (1942)). Festgehalten werden sollte, da SHELLSORT fur vorsortierte oder mittelgroe Eingaben (wenige Tausend) schneller ist als nahezu alle bekannten schnellen Sortierverfahren. QUICKSORT (Hoare (1962)) ist das wohl bekannteste, auf einer Partitionierung der Schlusselmenge beruhende Sortierverfahren. Damit liegt der wesentliche Aufwand des Divide-and-Conquer Ansatzes im divide{Schritt (n 1 Vergleiche). Dort wird die endgultige Position k eines ausgewahlten Elementes x ermittelt und die Schlusselmenge in einen von x dominierten bzw. in einen x dominierenden Teil eingeteilt. Der Algorithmus benotigt bei ungunstiger Aufteilung der Objektmenge (n2) viele Vergleiche. Fur den average-case an Vergleichen V (n) ndet sich durch die Mittelung der 225 moglichen Falle folgende Rekursionsgleichung: 8 < V (n) = : 0n 1 + 1 Pn (V (k 1) + V (n k)) n k=1 n 2 f0; 1g n2 Diese Summe lat sich zu folgender Gleichung vereinfachen: V (n) = 2(n + 1)Hn 4n; wobei Hn = Pni 1=i die n-te Partialsumme der Harmonischen Reihe bezeichnet. Die =1 Harmonische Reihe lat sich gut approximieren und man erhalt fur die mittlere Anzahl der Vergleiche: V (n) 1:386n log n 2:846n + O(log n) CLEVER-QUICKSORT ist eine von Hoare (1962) vorgeschlagene verbesserte Vari- ante von QUICKSORT. Da das Partitionselement weit vom Median entfernt sein kann, wird das mittlere Element x von drei zur Partition vorgeschlagenen Objekten ermittelt, und die Partitionierung der ubrigen Objekte bezuglich dieses Elementes durchgefuhrt. Bei ungunstiger Aufteilung der Objektmenge sind wieder (n2) viele Vergleiche notig, doch der average-case wird deutlich gesenkt. Der Median dreier Objekte kann in durchschnittlich 8/3 Vergleichen gefunden werden, was leicht durch Fallunterscheidung zu verizieren ist. Damit benotigt der divide{Schritt im Mittel n 3 + 8=3 = n 1=3 Vergleiche. n Die Wahrscheinlichkeit, da x an der Position k steht, ist gleich (k 1)(n k)= 3 , da es dann k 1 Positionen fur das kleinere und n k Positionen fur das groere Objekt gibt. Fur den average-case an Vergleichen V (n) gilt demnach folgende Rekursonsgleichung: 8 > 0 > < V (n) = > 1 > :n n 2 f0; 1g n=2 n Pn + (k 1)(n k)(V (k 1) + V (n k)) n 2 k Diese Summe lat sich fur n 6 zu folgender Gleichung zusammenfassen (Wegener (1995)): 1 3 1 3 =1 223 + 252 V (n) = 127 (n + 1)Hn 477 n + 147 147 147n 1:188n log n 2:255n + O(log n): Median nicht aus drei sondern aus 2k + 1 Elementen gebildet, 1 Wird der lat sich das mittlere Laufzeitverhalten noch verbessern und es ergibt sich eine Komplexitat von KAPITEL 10. THEORIE 226 kn log n + O(n) mit konstanten k > 1. Bei wachsendem k konvergiert k gegen 1 (vergl. van Emden (1970)). Derzeit ist keine QUICKSORT-Variante bekannt, deren average-case durch n log n + o(n log n) beschrankt ist. HEAPSORT Die worst-case Zahl wesentlicher Vergleiche von HEAPSORT ist durch 2n log n + (2 2c(n))n + 2blog 2c beschrankt. Fur den Aufbau eines Heaps genugen 2n 2 wesentliche Vergleiche. BOTTOM-UP-HEAPSORT benotigt nicht mehr als 1:5n log n + (2 c(n))n + O(log2 n) Vergleiche. Fleischer (1991) sowie Schaer und Sedgewick (1993) haben worst- case Beispiele angegeben, bei denen die Anzahl der wesentlichen Vergleiche fur BOTTOMUP-HEAPSORT gleich 1:5n log n o(n log n) ist. Die Durchschnittsanzahl von Vergleichen fur BOTTOM-UP-HEAPSORT ist hochstens n log n + O(n). WEAK-HEAPSORT erfullt alle 7 Kriterien. Die Schlusselvergleichsanzahl im ungunstigsten Fall ist durch n log n + 0:1n beschrankt. Empirische Studien belegen, da der mittlere Fall bei n log n + d(n)n mit d(n) aus dem Intervall [ 0:47; 0:42] liegt. Die Beweise zu den letzten drei Abschnitten nden sich in Weak-Heapsort, ein schnelles Sortierverfahren, Diplomarbeit Stefan Edelkamp. Aufgabe 46: Wir wollen den folgenden Sachverhalt zeigen: Sei k = dlog ne. Die maximale Anzahl von Schlusselvergleichen von WEAKHEAPSORT ist nk 2k + n 1 n log n + 0:086013n. Der Beweis lauft wie folgt: Es gibt durch die Aufrufe combine(i) fur i = n nX1 i=2 1; : : : ; 2 hochstens dlog(i + 1)e = nk 2k (10.1) Vergleiche. Zusammen mit den n 1 Vergleichen, die zum Aufbau des Heaps benotigt werden, sind dies hochstens nk 2k + n 1 Vergleiche fur den gesamten Sortieralgorithmus. 227 Fur alle n 2 N existiert ein x 2 [0; 1[, so da: nk 2k + n 1 = n log n + nx n2x + n 1 = n log n + n(x 2x + 1) 1: Die reelle Funktion f (x) = x 2x + 1 ist nach oben durch 0.086013 beschrankt. Damit ist die Anzahl an Vergleichen geringer als n log n + 0:086013n. Zeigen Sie: (a) Pni=1 i2i = (n 1)2n+1 + 2 durch vollstandige Induktion. (b) Pni=1dlog ie = nk 2k + 1 durch eine geschickte Aufteilung der Summe nach jeweils 2i Elementen. Sie benotigen dann zur Losung die Gleichung aus dem ersten Aufgabenteil. (c) Die reelle Funktion f (x) = x 2x +1 ist fur x 2 [0; 1[ nach oben durch 0.086013 beschrankt. Losung zur Aufgabe 46: =1 (a) Induktionsanfang n = 1: 2 = 2. Induktionsschritt: Pni=0 i 2i = (n + 1)2n+1 + (n 1)2n+1 + 2 = n2n+2 + 2 (b) n X dlog ie = i=1 dlogX ne 1 i2i + dlog ne(n 2d i=1 dlogX n 1e 1 log ne 1 ) = 21 ( i2i) + ndlog ne 21 dlog ne2dlog ne i=1 1 HS = 2 ((dlog ne 2)2dlog ne + 2) + ndlog ne 12 dlog ne2dlog ne = 12 dlog ne2dlog ne 2dlog ne + 1 + ndlog ne 21 dlog ne2dlog ne = ndlog ne 2dlog ne + 1: KAPITEL 10. THEORIE 228 (c) f (x) = x ex + 1: f 0(x) = 1 ex ln 2 = 1 2x ln 2: f 00(x) = 2x(ln 2) : Nach dem notwendigen Kriterium fur ein Extremum f 0(x ) = 0 ergibt sich: ln 2 ln 2 2 0 1 2x0 ln 2 = 0 2x0 = ln12 x0 ln 2 = ln 1 ln ln 2 x0 = lnlnln22 : Fur x 2 [0; 1] ist f 00(x) kleiner als Null. Es gilt f(0) = f(1) = 0. Somit ist Maximalstelle. x 0 Kapitel 11 Praxis Aufgabe 47: Die Quadratwurzel einer positiven reelen Zahl a lat sich naherungsweise uber die folgende Iterationsformel bestimmen: xn+1 = (xn + a=xn)=2. Schreiben sie ein Java Programm Wurzel, das die Wurzel der Eingabe berechnet. Dabei gilt ein Iterationswert als gut genug, falls er von dem nachfolgenden Wert um nicht mehr als 0.00001 abweicht. Deklarieren Sie die Variablen im Bereich des Typs double, weisen Sie zur Verarbeitung der Eingabe a den Wert Double.valueOf(args[0]).doubleValue() zu, starten Sie mit dem Iterationswert 1, und nutzen Sie zur Berechung des Absolutbetrages die Funktion Math.abs. Losung zur Aufgabe 47: class Wurzel { public static void main(String args[]) { double x = 1; double a = Double.valueOf(args[0]).doubleValue(); while (Math.abs(x - 0.5 * (x + a/x)) > .00001) { x = 0.5 * (x + a/x); System.out.println("Die Wurzel von "+a+" ist " + x); } } } Bemerkung: Die Formel ergibt sich aus dem (schnell konvergierenden) Iterationsverfah- ren nach Newton. Um eine Nullstelle einer Funktion f zu nden, nutzt man die allgemeine Iterationsvorschrift: xn+1 = xn f (xn)=f 0(xn). In unserem Fall ist f (x) = x2 a und f 0(x) = 2x. Weiteres Beispiel: Nullstellensuche in f (x) = x3 2:6x2 2:25x + 5:85. 229 KAPITEL 11. PRAXIS 230 Die Ableitung f 0(x) ist gleich 3x2 5:2x 2:25. Zum Startwert 1 ndet sich die Nullstelle 1.5, d.h. f (x) = (x 1:5)(x2 1:1x 3:9). Die weiteren Nullstellen -1.5 und 2.6 lassen sich nun direkt bestimmen. Aufgabe 48: Gegeben seien folgende Denitionen: f (n) = g(n; 0) und g(n; i) = 0, falls n 0 gilt, und g(n; i) = g(n 1 2i; i + 1) + 1, sonst. Schreiben sie ein Java-Programm Funktion, das fur jedes ubergebene Argument n aus den naturlichen Zahlen den Wert f (n) berechnet und ausgibt! Konnen Sie in Worten beschreiben, welche Funktion durch f berechnet wird? Losung zur Aufgabe 48: public class Funktion { static int g(int n, int i) { System.out.println("g("+n+","+i+") ="); if (n<=0) return 0; return g(n-1-2*i,i+1)+1; } public static void main(String args[]) { int x = Integer.parseInt(args[0]); System.out.println(g(x,0)); } } Es wird die (nach oben abgerundete) ganzzahlige Wurzel von n berechnet. Die Berechnung der Wurzel beruht auf dem Zusammenhang 1 + 3 + 5 : : : + 2n 1 = n2. Aufgabe 49: In Rekursien treen sich die drei Einwohner Herr Ackermann, Herr Fibonacci und Frau Ulam. Bald ist eine heftige Diskussion uber die Schonheit der von Ihnen erdachten Funktionen entbrannt. Herr Ackermann betont das auergewohnliche Wachstum seiner 231 Funktion, Herr Fibonacci unterstreicht den inneren Zusammenhang der Funktionswerte, wahrend Frau Ulam sich an den vielen Wegen zur Eins erfreut . . . (a) In der Zwischenzeit sollen Sie zeigen, wie leicht sich alle drei Funktionen in Java implementieren lassen. Berechnen Sie fur Herrn Ackermann a(3; 5), fur Herrn Fibonacci F (25) und fur Frau Ulam ulam (71). Als Hinweis soll Ihnen die Implementation der Ackermannfunktion dienen: public class Acker { static int a(int x, int y) { System.out.println("a("+x+","+y+") ="); if (x==0) return y+1; if (y==0) return a(x-1,1); return a(x-1,a(x,y-1)); } public static void main(String args[]) { int x = Integer.parseInt(args[0]); int y = Integer.parseInt(args[1]); System.out.println(a(x,y)); } } (b) Erweitern Sie Ihre Programme so, da zusatzlich jeweils die Anzahl der rekursiven Aufrufe ausgegeben wird. Losung zur Aufgabe 49: (a) public class Fibonacci { static int F(int x) { System.out.println("F("+x+") ="); if (x<=1) return x; return F(x-1)+F(x-2); } public static void main(String args[]) { int x = Integer.parseInt(args[0]); System.out.println(F(x)); } } public class Ulam { KAPITEL 11. PRAXIS 232 static long ulam(long y) { System.out.println("ulam("+y+") ="); if (y<=1) return 1; if (y%2 == 0) return ulam(y/2); else return ulam(3*y+1); } public static void main(String args[]) { int x = Integer.parseInt(args[0]); System.out.println(ulam(x)); } } (b) public class Fibonacci { static int count; static int F(int x) { count++; System.out.println("F("+x+") ="); if (x<=1) return x; return F(x-1) + F(x-2); } public static void main(String args[]) { count = 0; System.out.println(F(x)); System.out.println(" Anzahl der Rekursionsaufrufe = " + count); } } Aufgabe 50: Registermaschinen sind ein idealisiertes Berechungsmodell und spiegeln die imperative Struktur ausfuhrbarer Programme wider. In dieser Aufgabe wollen wir die Arbeitsweise einer Registermaschine mit einem Java Programm Register simulieren. Hierbei beschranken wir uns auf eine feste Anzahl von vier Registern R[1] bis R[4] und auf Registerinhalte des Datentyps long. Innerhalb der Klasse Register denieren wir deshalb als erstes ein statisches Array R mit static long[] R = new long[5]. Glucklicherweise werden damit auch schon alle Registerinhalte mit 0 initialisiert. (a) Die Funktionen static void a(int i) ... bzw. static void s(int i) ... fur die Registermaschinenbefehle ai bzw. si konnen Sie durch Manipulation von R[i] sicherlich an den Stellen ... gema ihrer Denition erganzen! 233 (b) In der Hauptroutine main soll die Simulation aufbauend auf die Registermaschinendenition und die Funktionen aus Teil (a) durchgefuhrt werden. Lesen Sie als erstes einen Parameter in das Feld R[1] ein und geben Sie das Register R[2] aus. (c) Simulieren Sie das Registermaschinenprogramm zur Berechnung von d Hinweise: pne. Das Schreiben des ersten Registerinhaltes in das zweite und dritte Register durch (s a a ) wird zu while (R[1] != 0) f s(1); a(2); a(3); g. Nutzen Sie die gewonnenen Erkenntnisse zur Berechnung von dpne aus Auf1 2 3 1 gabe 1 des U bungsblattes 3 (Theorie 2), d.h. realisieren Sie das Zuruckschreiben des zweiten Registers in das erste und das Registermaschinenprogramm ((s2s3a4)2(s4s3a2)4s3a2)3. Losung zur Aufgabe 50: (a) (b) (c) public class Register { static long[] R = new long[5]; static void a(int i) { R[i]++; } static void s(int i) { if (R[i] > 0) R[i]--; } ... } ... public static void main (String args[]) { R[1] = Long.valueOf(args[0]).longValue(); ... System.out.println(R[2]); } ... ... while (R[1] != 0) { while (R[2] != 0) { while (R[3] != 0) { while (R[2] != 0) while (R[4] != 0) s(3); a(2); s(1); a(2); a(3); } s(2); a(1); } { s(2); s(3); a(4); } { s(4); s(3); a(2); } KAPITEL 11. PRAXIS 234 } ... Aufgabe 51: Fur den Inhalt A eines unregelmaigen n-Ecks in der Ebene mit den (entgegen dem Uhrzeigersinn gelesenen) Endpunkten (x0; y0); (x1; y1); : : : ; (xn; yn) = (x0; y0) gilt: nX1 1 A = 2 (xkyk+1 xk+1yk) k=0 (a) Schreiben Sie ein Java Programm Polygon, das die Koordinaten der vier Punkte in der Reihenfolge x0,y0,x1,y1,. . . ,x3 ,y3 einliest und den Flacheninhalt dieses Vierecks berechnet. Nutzen Sie dabei die Arrays x und y vom Typ double (Initialisierung z.B. von x durch den Aufruf double[] x = new double[5] und Einlesen z.B. von x[0] durch den Aufruf x[0] = Double.valueOf(args[0]).doubleValue()). Fuhren Sie die Berechnung des Flacheninhalts A in der Schleife for(int i=0;i<4;i++) durch. (b) Erweitern Sie Ihr Programm von Vier- auf n-Ecke. Dabei sei n der erste Aufrufparameter. Losung zur Aufgabe 51: (a) public class Polygon { public static void main (String args[]) { double [] x = new double[5]; double [] y = new double[5]; x[0] = Double.valueOf(args[0]).doubleValue(); y[0] = Double.valueOf(args[1]).doubleValue(); x[1] = Double.valueOf(args[2]).doubleValue(); y[1] = Double.valueOf(args[3]).doubleValue(); x[2] = Double.valueOf(args[4]).doubleValue(); y[2] = Double.valueOf(args[5]).doubleValue(); x[3] = Double.valueOf(args[6]).doubleValue(); y[3] = Double.valueOf(args[7]).doubleValue(); x[4] = x[0]; y[4] = y[0]; double A = 0; for(int i=0;i<4;i++) A += x[i]*y[i+1] - y[i]*x[i+1]; System.out.println("Inhalt = " + A/2.0); 235 } } (b) public class Polygon { public static void main (String args[]) { double A = 0; int n = Integer.parseInt(args[0])+1; double [] x = new double[n]; double [] y = new double[n]; for(int i=0;i<n-1;i++) { x[i] = Double.valueOf(args[2*i+1]).doubleValue(); y[i] = Double.valueOf(args[2*i+2]).doubleValue(); } x[n-1] = x[0]; y[n-1] = y[0]; for(int i=0;i<n-1;i++) A += x[i]*y[i+1] - y[i]*x[i+1]; System.out.println("Inhalt = " + A/2.0); } } Bemerkung: Die Summe ergibt sich aus der sogenannten Sektorformel, mit der sich die Flache eines Gebietes als Integral uber den Rand berechnen lat. Aufgabe 52: Wir beweisen den folgenden Satz konstruktiv durch die Angabe eines Programmes: Ist der grote gemeinsame Teiler von a und b, kurz ggT (a; b), mit a; b 2 IN gleich d, so gibt es ein x und ein y mit ax + by = d. Dazu schauen wir uns den euklidischen Algorithmus ggT (a; 0b) =0 ggT (b; a mod b) mit 0ggT (a; 0)0 = a genauer an. Zu00 a und b gibt es ein 00 Paar (x ; y ) = (1; 0) mit a = x a + y b und ein Paar (x ; y ) = (0; 1) mit b =0 x00 a00 + y00 b0 . Fur a00 mod b = a qb mit q = a div b ndet sich die Darstellung (x qx )a + (y qy )b. (a) Schreiben Sie aufbauend auf diesen Sachverhalt ein Java Programm GGT zur Berechnung von x und y. Dabei sollen Sie x und y als statische Klassenvariablen durch public static long x,y festgelegen und eine rekursive, d.h. sich selbst aufrufende, Prozedur public static long ggt mit den sechs Parametern long a, long xa, long ya, long b, long xb und long yb programmieren (xa steht f ur x0, ya steht fur y0, xb steht fur x00 und yb fur y00). Ist der Parameter b gleich Null, so werden x und y auf xa bzw. ya festgelegt und a zuruckgegeben. Der Aufruf von ggt aus dem Hauptprogramm main geschieht durch long d = ggt(a,1,0,b,0,1), wobei sich a und b wiederum aus den Aufrufparametern ergeben. Hinweise: Der KAPITEL 11. PRAXIS 236 Java-Ausdruck a%b berechnet a mod b und der Ausdruck xa-(a/b)*xb berechnet x0 (a div b)x00. Testen Sie Ihr Programm an dem Beispiel a = 168 und b = 62 (Die Ausgabe des Ausdrucks System.out.println(d+"="+x+"*"+a+"+ "+y+"*"+b) im Hauptprogramm sollte 2=-7*168+ 19*62 ergeben). (b) Erganzen Sie das Java Programm durch die Berechnung des kleinsten gemeinsamen Vielfachen kgV . Hinweis: kgV (a; b) = ab=ggT (a; b). Losung zur Aufgabe 52: (a) (b) public class GGT { public static long x,y; public static long ggt(long a,long xa,long ya,long b,long xb,long yb) { if (b == 0) { x=xa; y=ya; return a; } return ggt(b,xb,yb,a%b,xa-(a/b)*xb,ya-(a/b)*yb); } public static void main(String args[]) { long a = Long.valueOf(args[0]).longValue(); long b = Long.valueOf(args[1]).longValue(); long d = ggt(a,1,0,b,0,1); System.out.println(d+"="+x+"*"+a+"+ "+y+"*"+b); ... } } System.out.println("Das kgV von "+a+" und "+b+" ist "+a*b/d); Aufgabe 53: Eine Liste ist eine (einfach verkettete) dynamische Datenstruktur, in der am Listenanfang Kopf Elemente eingef ugt und entfernt werden konnen. Vervollstandigen Sie die folgende Java Spezikation einer Liste von Ganzzahlen (Liste.java). class Element { Element succ; int i; // Nachfolger und Wert Element(Element succ, int i) { this.succ = succ; this.i = i; } } public class Liste { Element Kopf; // Listenanfang 237 Liste() { Kopf = null; } // initialisiere Listenanfang public void Einfuegen(int i) ... // eine Ganzzahl hinzufuegen public void Entfernen() ... // Ganzzahl am Listenanfang enfernen public String toString() ... // Ausgabe (s. Praesenzuebung) public static void main(String args[]) { Liste L = new Liste(); // Konstruktoraufruf for(int i=0;i<10;i++) { L.Einfuegen(i); System.out.println(L); } for(int i=0;i<5;i++) { L.Entfernen(); System.out.println(L); } } } Losung zur Aufgabe 53: public Kopf } public Kopf } void Einfuegen(int i) { = new Element(Kopf,i); void Entfernen() { = Kopf.succ; StringBuffer st = new StringBuffer(""); for (Element l=Kopf;l!=null;l=l.succ) st.append(l.i+" "); return st.toString(); Aufgabe 54: Gegeben sei folgende abstrakte Klasse Form (Form.java) einer Flachenform. public abstract class Form { public abstract double Flaeche(); // berechnet Flaecheninhalt public abstract double Umfang(); // berechnet Umfang public abstract String toString(); // erzeugt einen String fuer beides } Programmieren Sie fur die folgende generische Testprozedur (FormTest.java) die abgeleiteten Klassen Rechteck (Rechteck.java) und Kreis (Kreis.java). public class FormTest { public static void main (String[] args) { Form[] f = new Form[2]; // Array von zwei Formen KAPITEL 11. PRAXIS 238 f[0] = new Kreis(3); // Kreis mit Radius 3 f[1] = new Rechteck(3,4); // Rechteck der Groesse 3 mal 4 for(int i=0;i<f.length;i++) { System.out.println(f[i]); } } } Losung zur Aufgabe 54: public class Rechteck extends Form { protected double b, l; public Rechteck(int b, int l) { this.b = b; this.l = l; } public double Flaeche() { return b*l; } public double Umfang() { return 2*b*l; } public String toString() { StringBuffer st = new StringBuffer("Rechteck."); st.append(" Breite:"+b+ " Laenge:"+l+" Flaeche:"+ Flaeche()+" Umfang:"+Umfang()); return st.toString(); } } public class Kreis extends Form { protected double r; public Kreis(double r) { this.r = r; } public double Flaeche() { return PI*r*r; } public double Umfang() { return 2*Math.PI*r; } public String toString() { StringBuffer st = new StringBuffer(" Kreis."); st.append(" Radius:"+r+" Flaeche:"+Flaeche()+" Umfang:"+Umfang()); return st.toString(); } } Aufgabe 55: Ein Korper ist eine algebraische Struktur, die axiomatisch festgelegt ist und eine additive und multiplikative Verknupfung besitzt. Beispiele sind die rationalen, die reellen und die komplexen Zahlen und der Restklassenring Z/pZ zur Primzahl p. Implementieren Sie aufbauend auf die folgende abstrakte Klasse (Koerper.java) die abgeleitete Klasse Bruchzahl (Bruchzahl.java), die die angegebenen Operationen fur gekurz- 239 te Bruche verwirklicht. Nutzen Sie hierbei die Berechnung des ggT 's nach Euklid. Der Zahler soll eine ganze, der Nenner eine naturliche Zahl sein. public abstract class Koerper { public abstract Koerper Null(); // public abstract Koerper Eins(); // public abstract Koerper addInverses(); // public abstract Koerper multInverses(); // public abstract boolean isEqual(Koerper q); public abstract Koerper add(Koerper q); // public abstract Koerper mult(Koerper q); // public abstract String toString(); // } Nullelement Einselement Inverses der Addition Inverses der Multiplikation // Gleichheit Addition Multiplikation Konvertierung in String Hinweise: Der Parameter q in mult, add und in isEqual mu durch das Voranstellen von (Bruchzahl) explizit gecasted werden. Der Konstruktor in public class Bruchzahl extends Koerper k onnte wie folgt aussehen: Bruchzahl(int z, int n) { if (n == 0) { System.out.println("Division durch Null"); System.exit(1); } if (n < 0) { this.z = -z; this.n = -n; } else { this.z = z; this.n = n; } int g = ggT(Math.abs(z),n); this.z /= g; this.n /= g; } Testen Sie ihr Programm in einer Klasse BruchTest (BruchTest.java) mit verschiedenen Bruchaufgaben, z.B. mit (2/6 - 4/8)/6. Losung zur Aufgabe 55: public class Bruchzahl extends Koerper { int z; // Zaehler int n; // Nenner Bruchzahl(int z, int n) { if (n == 0) { System.out.println("Division durch Null"); System.exit(1); } if (n < 0) { this.z = -z; this.n = -n; } KAPITEL 11. PRAXIS 240 else { this.z = z; this.n = n; } int g = ggT(Math.abs(z),n); this.z /= g; this.n /= g; } Bruchzahl(int z) { this.z = z; n = 1; } public Koerper Null() { return new Bruchzahl(0,1); } public Koerper Eins() { return new Bruchzahl(1,1);} public Koerper addInverses() { return new Bruchzahl(-z,n); } public Koerper multInverses() { return new Bruchzahl(n,z); } int ggT(int a, int b) { if (a == 0) return b; if (b == 0) return a; if (a < b) return ggT(a,b%a); else return ggT(a%b,b); } public boolean isEqual(Koerper t){ Bruchzahl q = (Bruchzahl) t; return ((z == q.z) && (n == q.n)); } public Koerper add (Koerper t) { Bruchzahl q = (Bruchzahl) t; return new Bruchzahl ( n * q.z + z * q.n, n * q.n ); } public Koerper mult(Koerper t) { Bruchzahl q = (Bruchzahl) t; return new Bruchzahl( z * q.z, n * q.n ); } public String toString() { StringBuffer st = new StringBuffer(""); if (n == 1) st.append(z); else st.append(z+"/"+n); return st.toString(); } } Aufgabe 56: Gehen Sie fur diese Aufgabe von folgendem Verhalten handelnder Personen auf einer Buhne aus: Erhalt der groe Zauberer Merlin die Nachricht auftritt(n; k) mit n; k 2 , n > 0 und 0 < k n, dann erweckt er n Feen auf Avalon in den Positionen 1; : : : ; n. Anschlieend schickt er eine gruss()-Nachricht an die Fee auf Position k. IN 241 Erhalt Merlin die Nachricht berichte(), dann teilt er mit, wie oft er selbst seit seinem Auftritt gruss()-Nachrichten erhielt. Jede Fee kennt ihre Position p auf Avalon. Eine Fee reagiert nur dann auf eine Nachricht, wenn sie erweckt ist. Erhalt eine erweckte Fee eine gruss()-Nachricht, ist sie sofort nicht mehr erweckt , ehe sie aber ganz untatig wird, verschickt sie noch einige gruss()-Nachrichten: Eine an Merlin, eine an die Fee auf Position 3p + 1, falls diese existiert und p die eigene Position ist, und eine an die Fee auf Position p=2, wenn die eigene Position p eine gerade Zahl ist. Schreiben Sie ein Java-Programm Buehne.java, welches einen Auftritt von Merlin fur eine als Argument ubergebene Anzahl n von Feen und eine ebenfalls als Argument ubergebene Position k fur die erste von Merlin zu gruende Fee initiiert. Anschlieend soll Merlin berichten, wie oft er selbst gegrut wurde. Beispiel fur verschiedene Programmlaufe: > java Buehne 200 92 Ich bin Merlin und erhielt 65 gruss-Nachrichten. > java Buehne 200 93 Ich bin Merlin und erhielt 1 gruss-Nachricht. > java Buehne 200 94 Ich bin Merlin und erhielt 4 gruss-Nachrichten. Losung zur Aufgabe 56: class Merlin { static long zaehler; static void gruss () { zaehler++; } static void auftritt (int n, int k) { zaehler = 0; if (n <= 0 || k <= 0 || k > n) return; Fee.erwecken (n); Fee.avalon[k].gruss (); } static String berichte () { return "Ich bin Merlin und erhielt "+zaehler+ " gruss-Nachricht"+ (zaehler==1?".":"en."); } KAPITEL 11. PRAXIS 242 } class Fee { static Fee [] avalon; int position; boolean erweckt; Fee (int n) { position = n; erweckt = true; } void gruss () { while (erweckt) { erweckt = false; Merlin.gruss (); int j = 3 * position + 1 ; if (j < avalon.length) avalon [j] .gruss (); if (position%2 == 0) avalon [position/2] .gruss (); } } static void erwecken (int n) { avalon = new Fee [n+1]; for (int p=1; p <= n; p++) avalon [p] = new Fee (p); } } class Buehne { public static void main (String[] args) { int anzahl = Integer.parseInt (args[0]); int erste = Integer.parseInt (args[1]); Merlin.auftritt (anzahl, erste); System.out.println ( Merlin.berichte () ); } } Aufgabe 57: Die virtuelle Welt Virtu simuliert der unmittelbaren Erfahrungsbereich der Studenten und Studentinnen der Informatik I Vorlesung: Das Gebaude 82. Virtu besteht aus vier Raumen: Der Mensa, dem Flurbereich, dem Pool und dem Horsaal. Im Flur nden wir einen Kopierer, im Pool mehrere Computer und im Horsaal einen Projektor. In dieser (wenn auch leicht vereinfachten) Welt lassen sich Turen onen bzw. schliessen, Raume betreten, Sachen nehmen und wieder ablegen. Hier ein kleines Beispiel: Hallo. Ich heisse Studi. 243 Ich besitze: . Ich bin im Flur. Oben ist eine Eisentuer. Im Norden ist eine offene Fluegeltuer. Im Westen ist eine Holztuer. Inventar: Kopierer. Was Nun? gehe nord Hallo. Ich heisse Studi. Ich besitze: . Ich bin im Hoersaal. Im Sueden ist eine offene Fluegeltuer. Inventar: Projektor. Was Nun? nehme Projektor Hallo. Ich heisse Studi. Ich besitze: Projektor. Ich bin im Hoersaal. Im Sueden ist eine offene Fluegeltuer. Inventar: . (a) Vervollstandigen Sie den Spielercharakter Studi in dem beigefugten Programm. Hinweis: Es handelt sich bei jedem Funktionsaufruf um einen Einzeiler. (b) Erweitern Sie die virtuelle Welt Virtu nach ihren Vorstellungen. Sie konnen z.B. mehrere Gegenstande, mehrere Raume oder gar vom Programm aus gesteuerte Personen einbauen. Die schonsten und einfallsreichsten Programme werden von der Jury aus U bungsgruppenleitern und Assistenten ausgewahlt und am Ende der Vorlesung pramiert. // Virtuelle Welt "Virtu". Datei VirtuTest.java 244 KAPITEL 11. PRAXIS import java.io.*; // Utensilien in der virtuellen Welt class Sache { String name; Sache next; Sache(String n, Raum r) { name = n; r.l.add(this); } public String toString() { StringBuffer st = new StringBuffer(name); return st.toString(); } } // Liste zur Verwaltung von Sachen fuer Studi oder fuer Raeume class Liste { public Sache Kopf; Liste() { Kopf = null; } public void add(Sache s) { s.next = Kopf; Kopf = s; } public boolean member(Sache s) { for (Sache i = Kopf;i != null;i = i.next) if (i == s) return true; return false; } public boolean del(Sache s) { if (!member(s)) { System.out.println(s+" ist nicht da.\n"); return false; } if (Kopf == s) Kopf = Kopf.next; for (Sache i = Kopf;i != null;i = i.next) if (i.next == s) i.next = s.next; s.next = null; return true; } public String toString() { StringBuffer st = new StringBuffer(""); for (Sache i = Kopf;i != null;i = i.next) if (i.next == null) st.append(i); else st.append(i+", "); return st.toString(); } 245 } // Richtungen werden als Zeichenketten verarbeitet class Richtung { String name; Richtung(String n) { name = n; } public String toString() { StringBuffer st = new StringBuffer(name); return st.toString(); } } // (offene) Tueren in der virtuellen Welt verbinden Raeume class Tuer { public boolean offen; String name; Tuer (String n, Raum r1, Raum r2, Richtung d1, Richtung d2) { name = n; offen = false; r1.nebenan(r2,d1,this); r2.nebenan(r1,d2,this); } public String toString() { StringBuffer st = new StringBuffer(""); if (offen) st.append("offene "); st.append(name); return st.toString(); } } // Raeume haben in max. 6 Richtungen Tueren in Nachbarraeume class Raum { final static int MAX = 6; Liste l; String name; int nachbar; Richtung D[]; Tuer T[]; Raum R[]; Raum(String n) { nachbar = 0; name = n; l = new Liste(); D = new Richtung[MAX]; T = new Tuer[MAX]; R = new Raum[MAX]; } public void nebenan(Raum r, Richtung d, Tuer t) { R[nachbar] = r; D[nachbar] = d; T[nachbar++] = t; } 246 KAPITEL 11. PRAXIS public Raum gehe(Richtung r) { int i=0; while (D[i] != r && i < MAX) i++; if (i == MAX) return this; if (T[i].offen) return R[i]; else return this; } public String toString() { StringBuffer st = new StringBuffer("Ich bin "+name+".\n"); for (int i=0;i<nachbar;i++) st.append(D[i]+" ist eine "+T[i]+".\n"); st.append("Inventar: "+l+".\n"); return st.toString(); } } // Der Studi, der sich in der virtuellen Welt bewegt und dort agiert // Hier sind die Funktionsruempfe zu implementieren class Studi { Raum in; String name; public Liste l; Studi(String n, Raum r) { name = n; in = r; l = new Liste(); } public void schliesse(Tuer t) ... public void gehe(Richtung r) ... public void oeffne(Tuer t) ... public void nehme(Sache s) ... public void gebe(Sache s) ... public String toString() { StringBuffer st = new StringBuffer("Ich heisse "+name+".\n"); st.append("Ich besitze: "+l+".\n"); st.append(in); return st.toString(); } } // Die Definition der Welt und die Schnittstelle zum Benutzer class Virtu { public String readString() { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String s = ""; try { s = in.readLine();} catch(Exception e) { System.err.println(e); System.exit(1); } return s; 247 } public void loop(String name) { Richtung oben = new Richtung("Oben"), unten = new Richtung("Unten"), nord = new Richtung("Im Norden"), sued = new Richtung("Im Sueden"), west = new Richtung("Im Westen"), ost = new Richtung("Im Osten"); Raum pool = new Raum("im Pool"), flur = new Raum("im Flur"), hoersaal = new Raum("im Hoersaal"), mensa = new Raum("in der Mensa"); Tuer holztuer = new Tuer("Holztuer",pool,flur,ost,west), eisentuer = new Tuer("Eisentuer",flur,mensa,oben,unten), fluegeltuer = new Tuer("Fluegeltuer",hoersaal,flur,sued,nord); Sache computer = new Sache("Computer",pool), projektor = new Sache("Projektor",hoersaal), kopierer = new Sache("Kopierer",flur); Studi M = new Studi(name,flur); String e1 = "", e2 = ""; do { if (e1.equals("oeffne")) { if (e2.equals("Eisentuer")) M.oeffne(eisentuer); if (e2.equals("Fluegeltuer")) M.oeffne(fluegeltuer); if (e2.equals("Holztuer")) M.oeffne(holztuer); } if (e1.equals("schliesse")) { if (e2.equals("Eisentuer")) M.schliesse(eisentuer); if (e2.equals("Fluegeltuer")) M.schliesse(fluegeltuer); if (e2.equals("Holztuer")) M.schliesse(holztuer); } if (e1.equals("gehe")) { if (e2.equals("nord")) M.gehe(nord); if (e2.equals("sued")) M.gehe(sued); if (e2.equals("west")) M.gehe(west); if (e2.equals("ost")) M.gehe(ost); if (e2.equals("oben")) M.gehe(oben); if (e2.equals("unten")) M.gehe(unten); } if (e1.equals("nehme")) { if (e2.equals("Computer")) M.nehme(computer); if (e2.equals("Projektor")) M.nehme(projektor); if (e2.equals("Kopierer")) M.nehme(kopierer); } if (e1.equals("gebe")) { KAPITEL 11. PRAXIS 248 if (e2.equals("Computer")) M.gebe(computer); if (e2.equals("Projektor")) M.gebe(projektor); if (e2.equals("Kopierer")) M.gebe(kopierer); } System.out.println("\n\nHallo. "+M+"\nWas Nun?"); e1 = readString(); e2 = readString(); } while (true); } } // Die virtuelle Welt wird initialisiert und die Simulation kann beginnen public class VirtuTest { public static void main(String args[]) { Virtu V = new Virtu(); V.loop(args[0]); } } Losung zur Aufgabe 57: public public public public public void void void void void schliesse(Tuer t) { t.offen = false; } gehe(Richtung r) { in = in.gehe(r); } oeffne(Tuer t) { t.offen = true; } nehme(Sache s) { if(in.l.del(s)) l.add(s); gebe(Sache s) { if(l.del(s)) in.l.add(s); } } Aufgabe 58: (a) A ndern Sie das Programm HelloWorld.java so, da es als ersten Parameter einen Namen akzeptiert und dann Hallo <Name> ausgibt. Falls kein oder mehr als ein Parameter angegeben wird, soll stattdessen die Zeile Benutzung: java HelloName <name> ausgegeben werden. (b) Schreiben Sie ein Klasse Counter, die einen Zahler implementiert. Die einzige Methode getNext() gibt bei jedem Aufruf eine um 1 erhohte Zahl zuruck. Als erstes soll 1 zuruckgegeben werden. Der Zahlerwert soll nur Ableitungen der Klasse voll zur Verfugung stehen. Schreiben Sie ein Testprogramm fur die Klasse. 249 (c) Leiten Sie eine neue Klasse SetCounter von Counter ab, bei der im Konstruktor der Startwert angegeben werden kann. Auerdem soll der nachste Zahlerwert auch spater mit setNext() gesetzt werden konnen. (d) Welche Moglichkeiten gibt es, wenn eine Funktion mehr als einen Ruckgabewert hat? Schreiben Sie eine Funktion Kuerzen zum Kurzen von Bruchen. (e) Schreiben Sie eine Funktion Array zuruckgibt. , die die ersten primes(int) n Primzahlen in einem (f) Implementieren Sie eine Funktion double Kehrwert(double), die den Kehrwert einer Zahl zuruckgibt. Bei 0 soll eine eigene Exception erzeugt werden. Losung zur Aufgabe 58: public class HelloName { public static void main(String args[]) { if (args.length != 1) { System.err.println("Benutzung: java HelloName <name>"); System.exit(1); } System.out.println("Hallo "+args[0]); } } public class Counter { protected int ct=1; public int getNext() { return ct++; } public static void main(String[] args) { // Testprogramm Counter ctr=new Counter(); for(int i=0;i<10;i++) System.out.println(ctr.getNext()); } } public class SetCounter extends Counter { public SetCounter() { this(1); } public SetCounter(int value) { ct=value; } public void setNext(int value) { ct=value; } KAPITEL 11. PRAXIS 250 public static void main(String[] args) { // Testprogramm SetCounter ctr=new SetCounter(5); for(int i=0;i<5;i++) System.out.println(ctr.getNext()); ctr.setNext(100); for(int i=0;i<5;i++) System.out.println(ctr.getNext()); } } // // // // // // Da Kuerzen zwei Werte veraendert, ist es am besten, eine neue Klasse Bruch mit einer Methode kuerzen zu definieren. Alternativ koennte man Bruch als Object an eine Methode kuerzen uebergeben, so dass die Werte veraendert werden koennen. Als weitere Moeglichkeit koennte ein neues Bruch-Objekt als Rueckgabewert definiert werden. class Bruch { int zaehler=1; int nenner=1; public Bruch() {} public Bruch(int zaehler, int nenner) { this.zaehler=zaehler; this.nenner=nenner; } public String toString() { return zaehler+"/"+nenner; } public void kuerzen() { // Ineffizientes Verfahren zur Bestimmung des ggT! int min=Math.min(zaehler,nenner); int ggt; for(ggt=min;ggt>0;ggt--) if(zaehler%ggt==0 && nenner%ggt==0) break; zaehler/=ggt; nenner/=ggt; } } 251 public class Kuerzen { public static void main(String[] args) { Bruch b=new Bruch(4,6); System.out.print("Der Bruch "+b); b.kuerzen(); System.out.println(" lautet gekuerzt "+b+"."); } } public class Primes { public static boolean prim(int zahl) { // Ineffizienter Primzahltest for(int n=2;n<zahl;n++) if (zahl%n==0) return false; return true; } public static int[] primes(int n) { // Array instanziieren int[] ret=new int[n]; // Array fuellen int found=0, zahl=2; while(found<n) { if (prim(zahl)) { ret[found]=zahl; found++; } zahl++; } return ret; } public static void main(String[] args) { int[] pr=primes(100); for(int i=0;i<pr.length;i++) System.out.print(pr[i]+"\t"); System.out.println(); } } class KehrwertVonNullException extends ArithmeticException { KAPITEL 11. PRAXIS 252 } public class Kehrwert { public static double kehrwert(double x) throws KehrwertVonNullException { if (x==0) throw new KehrwertVonNullException(); return 1/x; } public static void main(String[] args) { double a; a=5; System.out.println("Kehrwert von "+a+" ist "+kehrwert(a)+"."); a=0; System.out.println("Kehrwert von "+a+" ist "+kehrwert(a)+"."); } } Aufgabe 59: Das Maxsummenproblem wurde in der Vorlesung wie folgt deniert: Finde ein IndexPaar (i; j ) in einem Array a[1..n] fur das f (i; j ) = ai + : : : + aj maximal ist. Als Rechenschritte zahlen arithmetische Operationen und Vergleiche. Implementieren Sie die vier verschiedenen Ansatze zur Losung des Problems. Falls sie nicht weiter kommen, nden Sie Implementationshilfen in Ottmann/Widmayer: Algorithmen und Datenstrukturen, 3. Auage, Seite 12. Der naive Algorithmus Es sollen alle f (i; j ) berechnet werden und dann der maximale f -Wert ermittelt werden. Oensichtlich genugen zur Berechung von f (i; j ) genau j i Additionen. Der Algoritmus startet mit max f (1; 1) und aktualisiert max wenn notig. Der normale Algorithmus Der naive Ansatz berechnet a1 + a2 fur f (1; 2); f (1; 3); : : : ; f (1; n), also (n 1)-mal. Besser geht's mit folgender Erkenntnis. Es gilt: f (i; j + 1) = f (i; j ) + aj Damit braucht man fur alle f (i; )-Werte genau (n +1 i) Additionen. Divide-And-Conquer Annahme: n = 2k. Unterteilung in drei Klassen: 253 1 i; j n=2 (Divide-And-Conquer) 1 i n=2 < j n (Direkt) n=2 < i j n (Divide-And-Conquer) g(i) = ai + : : : + an=2 und h(j ) = an=2+1 + : : : + aj Dann gilt: f (i; j ) = g(i) + h(j ) Um f zu maximieren, maximiere g und h einzeln. Berechne nacheinander g(n=2) = a[n=2]; g(n=2 1); : : : ; g(1) und den max. g Wert in (n=2 1) Vergleichen und (n=2 1) Additionen. Berechne h analog. Der clevere Algorithmus Wir wollen das Problem in [1::n] losen und verwalten dabei nach dem Lesen von ak in max den groten Wert von f (i; j ) aller Paare (i; j ) fur 1 i j k. Fur k = 1 setzen wir max auf a1. Wenn ak+1 gelesen ist, aktualisiere maxneu = maxfmaxalt +ak+1; ak+1g. Fur maxneu kommen folgende Paare in Frage: 1 i j k (maximaler Wert maxalt) 1 i k; j = k + 1 (maximaler Wert maxneu) Losung zur Aufgabe 59: (a) public class Naiv { int[] list; public Naiv(int [] list) { //Warnung: prueft die Liste nicht auf laenge!!! this.list=list; } public void showMaxSubArray(){ int max_so_far=list[1]; for(int j=1;j<list.length;j++){ for(int i=2;i<list.length;i++){ if (getSum(j,i)>max_so_far) { max_so_far=getSum(j,i); System.out.println("Index "+j+","+i); System.out.println("Summe(naiv):"+max_so_far); } } } } KAPITEL 11. PRAXIS 254 private int getSum(int vonIndex,int bisIndex){ int erg=list[vonIndex++]; while(vonIndex<=bisIndex) erg+=list[vonIndex++]; return erg; } } (b) (c) public class Normal extends Naiv{ public Normal(int[] list) { super(list); } public void showMaxSubArray(){ int max_so_far=list[1]; int sum_so_far; for(int j=1;j<list.length;j++){ sum_so_far=list[j]; for(int i=j+1;i<list.length;i++){ sum_so_far+=list[i]; if (sum_so_far>max_so_far) { max_so_far=sum_so_far; System.out.println("Index "+j+","+i); System.out.println("Summe(norm):"+max_so_far); } } } } } public class Divide{ static int[] list=new int[] {0,0}; public Divide(int[] list) { this.list=list; System.out.println("Ergebnis(divide):"+getMaxSum(1,list.length-1); } public int getMaxSum(int vonIndex, int bisIndex){ //Basisfall: Arraylaenge=1; if(vonIndex==bisIndex) {if(list[vonIndex]>=0) return list[vonIndex]; else ret urn 0;} //Divide: int middle=(vonIndex+bisIndex)/2; 255 System.out.println("middle:"+middle); int lmax=getMaxSum(vonIndex,middle); int rmax=getMaxSum(middle+1,bisIndex); //mittleres maximum int mmax1=0; int mmax2=0; for (int i=middle;i>=vonIndex;i--){ if (getSum(i,middle)>mmax1) mmax1=getSum(i,middle); } for (int i=middle+1;i<=bisIndex;i++){ if (getSum(middle+1,i)>mmax2) mmax2=getSum(middle+1,i); } int erg=max(lmax,max(rmax,mmax1+mmax2)); return erg; } private int max(int i,int j){ if (i>j) return i; else return j; } private int getSum(int vonIndex,int bisIndex){ int erg=list[vonIndex++]; while(vonIndex<=bisIndex) erg+=list[vonIndex++]; return erg; } } (d) public class Scan { int[] list; public Scan(int[] list) { this.list=list; } public void showMaxSubArray(){ int scanMax=0, bisMax=0; int i=1; while (i<list.length){ if (scanMax+list[i]>0) scanMax+=list[i]; else scanMax=0; bisMax=max(bisMax,scanMax); i++; } KAPITEL 11. PRAXIS 256 System.out.println("Ergebnis(scan):"+bisMax); } private int max(int i,int j){ if (i>j) return i; else return j; } } Aufgabe 60: In dieser Aufgabe sollen Matrixoperationen implementiert werden. Zur Erinnerung: Matrizen sind Tabellen bestehend aus Zahlen. Die Addition A + B und die Multiplikation AB fur zwei Matrizen A = (aij ) und B = (bij )Psind wie folgt deniert: C = A + B mit cij = aij + bij und C = AB mit cij = k aikbkj . Im ersten Fall mussen die Dimensionen von A und B ubereinstimmen, im zweiten Fall mu die Spaltenanzahl von A gleich der Zeilenanzahl von B sein. Ein Beispiel: 0 1 0 1 0 1 1 3 BB CC 3 1 6 2 @ A @ A Fur A = B @ 0 4 CA und B = 0 2 ist B + B = 0 4 und AB = 0 1 2 5 3 7C B B C B @ 0 8 CA : 6 8 Erganzen Sie die folgende Klasse Matrix einer ganzzahligen Matrix an den angedeuteten Stellen und testen Sie die Klasse mit dem angegebenen Beispiel. Hinweis: Zur Dimensionsabfrage des zwei-dimensionalen Arrays M konnen Sie die Variablen M.length und M[0].length nutzen. public class Matrix { public int M[][]; Matrix (int z, int s) { M = new int[z][s]; } public Matrix add (Matrix A) ... public Matrix mult (Matrix A) ... } Wenn Sie mochten, konnen Sie eine Matrix und die darauf agierenden Matrixoperationen anstatt fur Ganzzahlen fur Korperelemente und damit auch fur Bruchzahlen (Info I, Blatt 6, Aufgabe 3) verwirklichen. Losung zur Aufgabe 60: 257 public Matrix add (Matrix A) { Matrix C = new Matrix(A.M.length,A.M[0].length); for(int i=0;i<A.M.length;i++) { for(int j=0;j<A.M[0].length;j++) C.M[i][j] = M[i][j] + A.M[i][j]; } return C; } public Matrix mult (Matrix A) { Matrix C = new Matrix(M.length,A.M[0].length); for(int i=0;i<M.length;i++) { for(int j=0;j<A.M[0].length;j++) { C.M[i][j] = 0; for(int k=0;k<M[0].length;k++) C.M[i][j] = C.M[i][j] + (C.M[i][k] * A.M[k][j]); } } return C; } Aufgabe 61: Zum Sortieren von Elementen dient als Komplexitatsma die Anzahl der Schlusselvergleiche. Analog zu dem in der Vorlesung beschriebenen Interface Orderable benotigen wir hier ein (vereinfachtes) Interface Comparable zum Groenvergleich zweier Objekte. interface Comparable { public boolean greater (Comparable o); } Schreiben Sie eine Java-Klasse Names, die den Namen und Vornamen einer Person speichert und das Interface Comparable befriedigt. Der Vergleich zweier Personen sollte in der Reihenfolge Name, Vorname durchgefuhrt werden. Dabei werden die Strings lexikographisch abgeglichen. Nutzen Sie dabei die Funktion public int compareTo(String anotherString) aus der Klasse String. Aufgabe 62: Ein Ring ist eine Datenstruktur, die, wie der Name schon sagt, Elemente in einem Zyklus miteinander verbindet. Ringe konnen in zwei Teile aufgeteilt und auch miteinander verschmolzen werden. Der Ring besitzt einen Einstiegspunkt first an dem (Ganzzahl-) Elemente eingefugt werden konnen. Implementieren Sie den wie folgt spezizierten Datentyp Ring als doppelt verkettete Liste in Java. Erzeugen Sie im Testlauf einen Ring mit den Elementen 1..8, spalten Sie die Elemente 3..6 in einen neuen Ring ab und fugen Sie den zweiten Ring, beginnend mit seinem zweiten Element, hinter das erste Element des ersten Rings ein. 258 KAPITEL 11. PRAXIS class Item { Item prev, next; public int value; } public class Ring { private Item first; public Ring(); // Konstruktor fuer leeren Ring public boolean isEmpty(); // Abfrage auf leeren Ring public boolean isFirst(Item l); // Anfrage auf erstes Element public Item getFirst(); // Ausgabe erstes Element public Item getNext(Item l); // Ausgabe nachfolgendes Element public void append(int val); // fuegt Element mit Wert val an public Ring split(Item s, Item t); // trennt den Teilring [s..t] vom aktuellen Ring ab // und liefert ihn zurueck public void merge(Item insertPos, Ring r, Item beginWith); // fuegt den Ring r ab der Position beginWith in den aktuellen // Ring ein, und zwar hinter der Position insertPos public String toString(); } Losung zur Aufgabe 62: class Item { Item prev, next; public int value; } public class Ring { private Item first; public Ring() { first = null; } public boolean isEmpty() { return first == null; } public boolean isFirst(Item l) { return l == first; } public Item getFirst() { return first; } public Item getNext(Item l) { return l.next; } public void append(int val) { Item l = new Item(); l.value = val; if(first == null) { first = l.prev = l.next = l; } // ein Element else { // Item am "Ende" des Rings, also vor first, einhaengen first.prev.next = l; l.prev = first.prev; l.next = first; first.prev = l; } } public Ring split(Item s, Item t) { if(t.next == s) { // trenne den ganzen Ring ab Ring neu = new Ring(); neu.first = first; first = null; return neu; } else { // entferne [s..t] aus diesem Ring s.prev.next = t.next; t.next.prev = s.prev; 259 Ring neu = new Ring(); // erzeuge einen neuen Ring mit den Elementen [s..t] s.prev = t; t.next = s; neu.first = s; return neu; } } public void merge(Item insertPos, Ring r, Item beginWith) { insertPos.next.prev = beginWith.prev; beginWith.prev.next = insertPos.next; insertPos.next = beginWith; beginWith.prev = insertPos; r.first = null; } public String toString() { if(first == null) { return ""; } else { String back = ""; Item pos = first; do { back = back + pos.value; pos = pos.next; if(pos != first) back = back + " "; } while(pos != first); return back; } } public static void main(String args[]) { Ring r = new Ring(); r.append(1); r.append(2); r.append(3); r.append(4); r.append(5); r.append(6); r.append(7); r.append(8); System.out.println(r); System.out.println(); Ring.Link pos3 = r.getNext(r.getNext(r.getFirst())); Ring.Link pos6 = r.getNext(r.getNext(r.getNext(pos3))); Ring r2 = r.split(pos3, pos6); System.out.println("Ring 1: " + r); System.out.println("Ring 2: " + r2); System.out.println(); Ring.Link pos4 = r2.getNext(r2.getFirst()); r.merge(r.getFirst(), r2, pos4); System.out.println("Ring 1: " + r); System.out.println("Ring 2: " + r2); } } Aufgabe 63: Man kann die Implementierung einer Queue (Schlange) fur beliebige Objekte auf eine schon vorhandene Implementierung der entsprechenden Stack-Klasse zuruckfuhren, auch wenn das nicht sehr ezient ist. Die begonnene Java-Implementierung von Queue sei class Queue Stack s, Queue () empty () { z; { s = new Stack (); z = new Stack (); } { return s.empty (); } KAPITEL 11. PRAXIS 260 ... } Erganzen Sie die Implementierung der noch fehlenden Methode enqueue(Object , Object dequeue() o) und Losung zur Aufgabe 63: enqueue(Object o) f g s.push (o); f dequeue() while (!s.empty ()) z.push (s.pop ()); Object o = z.pop (); while (!z.empty ()) s.push (z.pop ()); return o; g Aufgabe 64: McDiarmid and Reeds Variante des BOTTOM-UP-HEAPSORT, kurz MDR-HEAPSORT, ermoglicht es, weitere Vergleiche einzusparen, indem alte Informationen verwendet werden. Es wird allerdings ein zusatzliches Array Info benotigt, das diese alten Informationen verwaltet. Die moglichen Belegungen von Info[j] sind unbekannt, links und rechts (festgelegt durch die Konstanten UNKNOWN, LEFT und RIGHT) und konnen mit zwei Bits codiert werden. Ist Info[j] = LEFT, dann enthalt das linke Kind ein kleineres Element als das rechte, im Falle Info[j] = RIGHT ist es entsprechend genau umgekeht. Wenn Info[j] = UNKNOWN gilt, ist keine Dominanz der Kinder untereinander bekannt. Erweitern Sie Bottom-Up-Heapsort um diesen Ansatz, indem Sie die Prozeduren LeafSearch und Interchange entsprechend modizieren. Losung zur Aufgabe 64: static int LeafSearch(int root, int size) { int j, i=0; Path[i++]=j=root; while(2*j < size) { if (Info[j] == LEFT) { 261 Path[i++]=j=2*j; } else if (Info[j] == RIGHT) { Path[i++]=j=2*j+1; } else if (A[2*j+1].less(A[2*j])) { Info[j] = LEFT; Path[i++]=j=2*j; } else { Info[j] = RIGHT; Path[i++]=j=2*j+1; } } if (2*j == size) Path[i++]=j=size; return j; } static void Interchange(int i,int j) { int k; Orderable v = A[Path[0]]; for(k=0;Path[k]<j;k++) { A[Path[k]] = A[Path[k+1]]; Info[Path[k]] = UNKNOWN; } A[Path[k]] = v; } Aufgabe 65: WEAK-HEAPSORT benotigt im MergeForest Schritt im ungunstigen Fall dlog(m +1)e und im gunstigen Fall dlog(m +1)e 1 viele Vergleiche. Die Gesamtanzahl der Vergleiche ist durch n 1 plus die Schlusselvergleiche in der Sortierphase durch MergeForest (m), fur m = n 1; : : : ; 2, bestimmt. In dieser Aufgabe soll EXTERNAL-WEAK-HEAPSORT in Java programmiert werden, ein Ansatz, der jeweils den best case erzwingt (und demnach mit n log n 0:9n Vergleichen) auskommt. Dabei gehen wir davon aus, da wir einen zusatzlichen Datenbereich A[n : : : 2n 1] besitzen, in dem wir die entnommenen Wurzelelemente schreiben konnen. Dazu nutzen wir ein Boolesches Array Active, das die derzeit aktiven Index-Positionen verwaltet. Falls Active[i] wahr ist, dann wurde der Schlussel an Position i noch nicht in KAPITEL 11. PRAXIS 262 den Bereich A[n : : : 2n 1] geschrieben und ist demanch noch aktiv fur den betrachteten Weak-Heap. Die Positionen A[n : : : 2n 1] werden in einer Schleifenvariablen call verwaltet. In Pseudo-Code gestaltet sich die auere Schleife nun wie folgt procedure EXTERNAL-WEAK-HEAPSORT i := n-1, call := 0 WHILE i > 1 WHILE (Active[i] = 0) i := i - 1 call := call + 1 MergeForest(i) Wir betrachten die sich ergebenen Falle in der Sortierphase nun genauer. In den Fallen b) und c) der folgenden Abbildung liegen m und x auf unterschiedlichen Leveln, d.h. depth(x) = depth(m) 1 = dlog(m + 1)e 1. In Fall a) gilt hingegen depth(x) = depth(m) = dlog(m + 1)e. 75 a) 35 32 10 x 75 b) 70 70 6 8 11 35 36 4 m 5 32 1 10 m 6 x 0 36 5 11 75 c) 70 35 x 32 6 0 11 36 4 5 m Demnach konnen wir immer dann ein Vergleich einsparen, wenn wir anstatt dem mElement am Ende des Arrays, ein Element am Ende des speziellen Pfades wahlen und den entsprechende Index nach dem MergeForest -Schritt deaktivieren. Zusatzaufgabe (keine Bewertung): U berlegen Sie weiterhin, ob sich EXTERNAL-WEAKHEAPSORT analog zu QUICK-HEAPSORT in eine QUICKSORT Variante wandeln lat und wie sich der zusatzliche Speicherbedarf reduzieren lat. Losung zur Aufgabe 65: Hier die gewandelte Prozedur MergeForest im Pseudo-Code. PROCEDURE MergeForest(m) x := 1 WHILE (2x + Reverse[x] < m) x := 2x + Reverse[x] IF (depth(x) = depth(m)) temp = x DO Merge(temp, x DIV 2); x := x DIV 2; WHILE (x > 0) 263 swap(a[2n - call],a[temp]) Active[temp] := 0 ELSE DO Merge(m,x); x := x DIV 2; WHILE (x > 0) Swap(a[2n - call], a[m]) Active[m] := O Aufgabe 66: Innerhalb eines zufallig mit Ganzzahlelementen (Schlusseln) a[0],. . . ,a[n-1] gefullten Arrays a soll das minimale und maximale Element gefunden werden. Schreiben Sie ein Java Programm MinMax, das zur gleichzeitigen Suche beider Elemente moglichst wenige (Schlussel-) Vergleiche durchfuhrt. Ihr Programm sollte die triviale Anzahl von 2(n 1) Vergleichen wesentlich unterbieten. Nutzen Sie eine Variable call, die protokolliert, wie haug ein Schlusselvergleich less bzw. greater durchgefuhrt wurde. (a) Schreiben Sie das Programm ohne Divide-and-Conquer. (b) Schreiben Sie das Programm mit Divide-and-Conquer. Zur Vereinfachung konnen Sie sich auf n = 2k fur ein ganzzahliges k beschranken. Losung zur Aufgabe 66: class mm { int max, min; mm(int i, int j) { min = i; max = j;} public String toString() { return (new StringBuffer("Min:"+min+" Max:"+max)).toString(); } } public class MinMax { static int call; static void shuffle(int[] a) { call = 0; for(int j=0;j<a.length;j++) a[j] = j; for(int j=t.length-1;j>=1;j--) { int ri = (int) (Math.random() * j); int t=a[ri]; a[ri]=a[j]; a[j]=temp; } } static boolean less (int i,int j) { call++; return i<j; } static boolean greater(int i,int j) { call++; return i>j; } static mm loop_minmax(int[] a) { int n = a.length, max = 0, min = n; for(int i=0;i<n/2;i++) KAPITEL 11. PRAXIS 264 if (greater(a[i],a[n-i-1])) { int t=a[i]; a[i]=a[n-i-1]; a[n-i-1]=t; } for (int i=1;i<(n+1)/2;i++) if (less(a[i],min)) min = a[i]; for (int i=n/2;i<n-1;i++) if (greater(a[i],max)) max = a[i]; return new mm(min,max); } static mm dc_minmax(int[] a, int x,int y) { // only for n=2^k if (y-x <= 1) if (less(a[x],a[y])) return new mm(a[x],a[y]); else return new mm(a[y],a[x]); mm left = dc_minmax(a,x,(x+y)/2), right = dc_minmax(a,(x+y)/2+1,y); int min = 0, max = 0; if (greater(left.max,right.max)) max = left.max; else max = right.max; if (less(left.min,right.min)) min = left.min; else min = right.min; return new mm(min,max); } static mm dc_minmax(int[] a) { return dc_minmax(a,0,a.length-1); } public static void main(String args[]) { int[] a = new int[Integer.valueOf(args[0]).intValue()]; shuffle(a); mm r = loop_minmax(a); // simple loop System.out.println(r+" Calls:"+call); shuffle(a); r = dc_minmax(a); // divide-and-conquer System.out.println(r+" Calls:"+call); } } Aufgabe 67: Einen Stack kann man sich als Rangiergleis mit Prellbock vorstellen (siehe Skizze). Die Operation push entspricht dem Einfahren eines Waggons von rechts auf das Rangiergleis und die Operation pop dem Ausfahren eines Waggons vom Rangiergleis auf das linke Gleis. Durch Mischen der push{ und pop{Operationen kann nun die Reihenfolge der Wagen 1; : : : ; n permutiert werden. So erzeugt die Folge push, pop, push, push, pop, push, push, pop, pop, pop die Permutation 1,3,5,4,2 der Zahlen 1,2,3,4,5. Ausfahrt Einfahrt Stack Abbildung 11.1: Einfahrt und Ausfahrt der Waggons auf dem Rangiergleis. Programmieren Sie einen Algorithmus RangierTest, der fur eine gegebene Permutation i1; : : : ; in der Zahlen 1; : : : ; n entscheidet, ob diese durch das obige Verfahren erzeugt 265 werden kann. Es bietet sich an, den Stapel Stack aus der Bibliothek java.util.Stack zu benutzen. Die wichtigen Operationen sind isEmpty(), peek(), pop() und push(Object). Losung zur Aufgabe 67: import java.util.Stack; class RangierTest { Stack myStack = new Stack(); // einen Stack brauchen wir private int[] per; // nimmt die Zahlenfolge auf RangierTest(int per[]) { this.per = per; } // Konstruktor public boolean teste() { int n = per.length, j = 1; myStack.push(new Integer(0)); // Markierung fuer Stack-Ende for (int i=0;i<n;i++) { while (!(myStack.peek().equals(new Integer(per[i]))) && (j <= n)) myStack.push(new Integer(j++)); if (!(myStack.peek().equals(new Integer(per[i])))) return false; myStack.pop(); } if (j == (n + 1)) return true; return false; } } public class Test { public static void main(String args[]) { int[] per = new int[args.length]; for(int i=0;i<args.length;i++) per[i] = Integer.parseInt(args[i]); RangierTest myRangierTest = new RangierTest(per); // Instanz erzeugen if (myRangierTest.teste()) // jetzt testen System.out.println("Die Permutation kann erzeugt werden!"); else System.out.println("Diese Permutation funktioniert nicht!"); } } Aufgabe 68: Arithmetische Ausdrucke lassen sich sowohl in der Inx-, in der Prex-, als auch in der Postxdarstellung beschreiben. So ist entspricht (12-3) * ~4 der Inxdarstellung dem Ausdruck (* (- 12 3) (~ 4)) in der Prexdarstellung und 12 3 - 4 ~ * . in der Postxdarstellung. Zur Unterscheidung mit dem binaren Minusoperator wahlen wir zur Vereinfachung die Tilde (~) als ein unares Minuszeichen. Wir betrachten die Postxdarstellung, die durch einen Punkt abgeschlossen wird. Sie kommt ohne Klammern aus und kann durch den Einsatz eines Stapels verarbeitet werden. Von links nach rechts vorranschreitend werden jeweils die Operanden auf den Stapel 266 KAPITEL 11. PRAXIS gelegt. Wird ein Operator erreicht, so werden die obersten Elemente vom Stapel genommen (pop) und das Ergebnis wieder hinaufgelegt (push). Schreiben Sie eine Java Klasse Postfix, die einen Postx Ausdruck auswertet. Testen Sie Ihr Programm mit dem obigen Ausdruck. Losung zur Aufgabe 68: import java.util.Stack; class Postfix { private Stack stapel; private String expression, err = ""; private int pos, result; Postfix(String s) { stapel = new Stack(); expression = s; pos = result = 0; } private char getNextChar() { return expression.charAt( pos++ ); } public int getResult() { return result; } // Gibt das Ergebnis zurueck public String geterr() { return err; } // Fehlermeldung public boolean evaluate() { // false bei Fehler, ansonsten true char c = getNextChar(); // Naechstes Eingabezeichen int res = 0; // Speichert Zwischenergebnisse while (c != '.') { if (c == '+' || c == '-' || c == '*' || c == '/') { if (stapel.isEmpty()) { err = "Zu wenig Operanden!"; return false; } int op2 = ((Integer)stapel.pop()).intValue(); if (stapel.isEmpty()) { err = "Zu wenig Operanden!"; return false; } int op1 = ((Integer)stapel.pop()).intValue(); switch ( c ) { case '+' : { res = op1 + op2; break; } case '-' : { res = op1 - op2; break; } case '*' : { res = op1 * op2; break; } case '/' : { if ( op2 == 0 ) { err = "Division durch 0!"; return false; } res = op1 / op2; break; } } stapel.push( new Integer( res ) ); c = getNextChar(); } else if (c == '~') { int op1 = ((Integer)stapel.pop()).intValue(); res = - op1; stapel.push( new Integer( res ) ); c = getNextChar(); } else if ( Character.isDigit( c ) ) { 267 res = 0; do { res = res * 10 + Character.getNumericValue( c ); c = getNextChar(); } while ( Character.isDigit( c ) ); stapel.push( new Integer( res ) ); } else if ( c == ' ' ) c = getNextChar(); else { err = "Falsches Eingabezeichen"; return false; } } result = ((Integer)stapel.pop()).intValue(); if ( !stapel.isEmpty() ) { err = "Zu wenig Operatoren"; return false; } return true; } } class PostfixTest { public static void main( String[] args ) { String PostfixExpression = "12 3 - 4 ~ * ."; Postfix exp = new Postfix( PostfixExpression ); if ( exp.evaluate() ) { // Ist der Postfixausdruck gueltig? System.out.println( "Das Ergebnis ist "+ exp.getResult() ); } else System.out.println( "Fehler: "+exp.geterr() ); } } Aufgabe 69: Gegeben sei das Feld a mit der folgenden Schlussel-Belegung: 0,2,6,8,12,34,75,120. Beschreiben Sie die Suche nach dem Schlussel 34 im obigen Feld a durch Angabe der Folge der ausgefuhrten Schlusselvergleiche, wenn als Suchstrategie exponentielle Suche zur Eingrenzung des Suchbereichs mit anschlieender linearer Suche angewandt wird. Mit dieser neuen Erkenntnis implementieren Sie die Klasse ExpList, die diese Strategie verfolgt. Der Algorithmus soll angeben, in welcher Suchphase (exponentielle od. lineare) er gerade ist und alle Schluesselvergleiche auf der Konsole protokollieren. Das aufrufende Testprogramm ndet sich im Netz an der gleichen Stelle wie die Sourcen der folgenden Aufgabe und gestaltet sich wie folgt: public class Test { public static void main(String[] args) { System.out.println("\nExpo-(2000)-Suche\n"); ExpList f = new ExpList(new int [] {0,2,6,8,12,34,75,120}); f.find(12); f.find(120); f.find(-1); } } 268 KAPITEL 11. PRAXIS Losung zur Aufgabe 69: public class ExpList { int [] list; public ExpList(int[] list){ //Voraussetzung: Liste sortiert und listenlaenge-1 ist eine Zweierpotenz this.list=list; } public boolean find(int key){ int i=1; while ((i<list.length)&&(key>list[i])) { System.out.println("Exponentielle Suche: Index "+i+ ": vergleiche "+key+">"+ list[i]); i=i+i; } if (i>=list.length) { System.out.println("Array-Grenze ueberschritten: NICHT GEFUNDEN!"); return false; } System.out.println("Suchintervall(der Indizes!): ("+ ((int)(i/2))+","+i+"]"); // also ist der Schluessel (wenn vorhanden) im Intervall (i/2,i] // Beginn der linearen Suche int j=(int)(i/2)+1; while (j<=i) { System.out.println("Lineare Suche : Index" + j + ": vergleiche "+ key + "==" + list[j]); if (list[j]==key) { System.out.println ("GEFUNDEN!!"); return true; } j++; } System.out.println("NICHT GEFUNDEN!!"); return false; } } 269 Aufgabe 70: Um die verschiedenen Selbstanordnungsstrategien besser bewerten zu konnen, vergleichen Sie das Verhalten der MF-, der FC, und der T-Regel fur eine Zugrisfolge s, indem sie fur die folgende zu statistischen Zwecken aus gelegte Tabellenklasse die fehlenden Klassen MFList, FCList, und TList implementieren. public class Tabelle { MFList MF; TList T; FCListe FC; int[] folge; int totalMF, totalT, totalFC; public Tabelle(int [] list, int[] zugriffsfolge) { MF = new MFList(list); int[] listT=new int[list.length]; for (int i=0;i<list.length;i++){ listT[i] = list[i];} T = new TList(listT); int[] listFC=new int[list.length]; for (int i=0;i<list.length;i++){ listFC[i] = list[i];} FC = new FCList(dummy2); folge = zugriffsfolge; totalMF = totalT = totalFC = 0; } public void starteVergleich(){ System.out.println("MFListe: "+MF+" | TListe: "+T+" | FCListe: "+FC); for (int i=0;i<folge.length;i++){ System.out.print("Zugriff auf Schluessel "+folge[i]+": "); int cnt = MF.access(folge[i]); totalMF += cnt; System.out.print("MF: "+MF+" Cost:"+cnt); cnt = T.access(folge[i]); totalT += cnt; System.out.println(" T: "+T+" Cost:"+cnt); cnt = FC.access(folge[i]); totalFC += cnt; System.out.println("FC: "+FC+" Cost:"+cnt); } System.out.println("-------------------------------------------------------------"); System.out.println("Gesamtkosten: MF "+totalMF+" , T "+totalT+" , FC"+totalFC); } } Testen Sie ihr Programm mit der Liste L = 1; 2; 3; 4; 5; 6; 7 und die Zugrisfolge s mit 21 Zugrien: 7; 2; 7; 3; 3; 7; 4; 4; 4; 7; 5; 5; 5; 5; 7; 6; 6; 6; 6; 6; 7 wie folgt public class Test { public static void main(String[] args) { Tabelle t = KAPITEL 11. PRAXIS 270 new Tabelle(new int [] {1,2,3,4,5,6,7}, new int [] {7,2,7,3,3,7,4,4,4,7,5,5,5,5,7,6,6,6,6,6,7}); t.starteVergleich(); } } Losung zur Aufgabe 70: public class MFList { private int[] list; public MFList(int[] list) { this.list=list; } public int access(int key){ int count=0; for (int i=0;i<list.length;i++){ count++; if (list[i]==key) i=list.length; } for (int i=(count-1);i>0;i--) swap(i,(i-1)); return count; } public String toString(){ String Erg=""; for (int i=0;i<list.length;i++){Erg+=" "+list[i];} return Erg+" "; } private void swap(int i, int j) { int dummy=list[i]; list[i]=list[j]; list[j]=dummy; } } public class TList{ private int[] list; private int bal; public TList(int[] list) { this.list=list; 271 int bal=0; } public int access(int key){ int count=0; for (int i=0;i<list.length;i++){ count++; if (list[i]==key) i=list.length; } if ((count-2)>=0) swap((count-1),(count-2)); return count; } public String toString(){ String Erg=""; for (int i=0;i<list.length;i++){Erg+=" "+list[i];} return Erg+" "; } private void swap(int i, int j){ int dummy=list[i]; list[i]=list[j]; list[j]=dummy; } } public class FCList { private Item[] list; public FCList(int[] intList) { list=new Item[intList.length]; for(int i=0;i<intList.length;i++) list[i]=new Item(intList[i]); } public int access(int key){ int count=0; for (int i=0; i<list.length;i++) { count++; if (list[i].key==key){ list[i].count++; while((i!=0)&&(list[i-1].count<list[i].count)){ swap(i,i-1); i--; KAPITEL 11. PRAXIS 272 } } } return count; } public String toString(){ String Erg=""; for (int i=0;i<list.length;i++){Erg+=" "+list[i];} return Erg+" "; } private void swap(int i, int j){ Item dummy=list[i]; list[i]=list[j]; list[j]=dummy; } } class Item { int key; int count; public Item(int key){ this.key=key; this.count=0; } public String toString(){ return ""+key; } } Aufgabe 71: Sortieren Sie mit naturlichen Suchbaumen! (a) Erzeugen Sie algorithmisch alle Permutationen von [1; 2; 3; 4; 5]. Nutzen Sie zur Aufzahlung eine rekursive Prozedur permute anstatt funf ineinandergeschachtelte Schleifen. Wieviel Vertauschungen benotigen Sie? (b) Gegeben eine Menge von n vergleichbaren (o.B.d.A. paarweise verschiedenen) Arrayelementen a[0] bis a[n-1]. Zu Vereinfachung beinhalte a eine Permutationen von [1; 2; 3; 4; 5]. Fugen Sie diese Elemente in einen naturlichen Suchbaum ein 273 und durchlaufen Sie den Baum in der symmetrischen Reihenfolge. Sie erhalten ein einfaches Sortierverfahren. Wie gut ist das Verfahren im besten, mittleren und schlechtesten Fall? Beantworten Sie diese Frage experimentell, indem Sie die Suchbaumimplementation aus der Vorlesung nutzen. Mitteln Sie uber alle Permutationen der Elemente in a. Losung zur Aufgabe 71: Es gibt Algorithmen (Pseudocode), die O(n!) viele Operationen benotigen. public void permute(int n) { if (n==0) { process(a); return; } for (int i=0;i<n;i++) b[i] = a[i]; permute(n-1); for (int i=0;i<n-1;i++) { swap(a[n-1],a[0]); permute(n-1); } for (int i=0;i<n;i++) a[i] = b[(i-1) \% n]; } Rekursion T (1) = c und T (n) = n T (n 1) + d(n 1) mit konstantem c und d. Weiterhin gibt es minimal-change-algorithmen die mit n! 1 swaps auskommen (siehe B.R. Heap. Permutations by interchanges. Computer Journal 6:293-294,1963). public void permute(int n) { for (int i=0;i<n-1;i++) a[i] = i+1; if (n\%2 == 1) oddpermute(n); else evenpermute(n); } public void oddpermute(n) { if (n==1) { process(a); return; } evenpermute(n-1); for (int i=0;i<n-1;i++) { swap(a[n-1],a[0]); evenpermute(n-1); }} public void evenpermute(n) { if (n==1) process(a); return; permute(n-1); for (int i=0;i<n-1;i++) { swap(a[n-1],a[i]); permute(n-1); }} public class TSort{ static int[]a; static int allCalls; // zaehlt die Vergleiche beim Einfuegen der Knoten static int calls,minsofar,maxsofar; 274 KAPITEL 11. PRAXIS public static void main (String args[]){ int b=Integer.parseInt(args[0]); //Das Array von der Laenge args[0] wird a=new int[b]; //mit einer aufsteigenden for (int i=0;i<b;i++) a[i]=i+1; //Folge von integers initialisiert. minsofar=999999; //999999 reicht hoffentlich permute (0); System.out.println("Gesamtvergleiche: "+allCalls+ " Anzahl der Permutationen:"+fak(b)); System.out.println("durchschnittliche Vergleichsanzahl: "+ ((double)allCalls/fak(b))); System.out.println("worst case Vergleiche: "+maxsofar); System.out.println("best case Vergleiche: "+minsofar); } public static void permute(int fromIndex){ //permutiere rechts von fromIndex if(fromIndex==a.length) { //falls nur eine Zahl zu permutieren ist for (int j=0;j<a.length;j++) // das aktuelle Array ist eine Permutation System.out.print(a[j]); System.out.print("-->"); calls=0; tSort(a); if (calls>maxsofar) maxsofar=calls; if (calls<minsofar) minsofar=calls; System.out.println(); } else{ // vertausche nacheinander alle Zahlen rechts von fromIndex for (int i=0;i<a.length-fromIndex;i++){ //mit der Zahl auf a[fromIndex] swap(fromIndex,fromIndex+i); permute(fromIndex+1); // permutiere den Rest swap (fromIndex+i,fromIndex); }}} //tausche die 2 Zahlen wieder zurueck public static void swap(int x,int y){ int dummy=a[x];a[x]=a[y];a[y]=dummy;} public static int fak(int n){ if (n<=1) return 1; else return n*fak(n-1); } public static void tSort(int a[]){ SearchTree T=new SearchTree(); for (int j=0; j<a.length;j++) T.insert (a[j]); T.inOrder(); }} class SearchTree extends TSort { SearchNode root; SearchTree(){root=null;} void insert(int c){ if (root==null) root=new SearchNode(c); else insert(root,c);} void insert(SearchNode current,int c){ //kuerzere Version if (current.key==c) return; //der Fall current.key==c else { //und wird deshalb als Vergleich nicht mitgezaehlt 275 allCalls++;calls++; //vergleiche werden gezaehlt if (c<current.key) { if (current.left!=null) insert(current.left,c); else current.left=new SearchNode(c); } else { //also c> current.key if (current.right!=null) insert(current.right,c); else current.right=new SearchNode(c); }}} void inOrder() {inOrder (root);System.out.println();} void inOrder(SearchNode n){ if (n==null) return; inOrder (n.left); System.out.print(n.key+" "); inOrder (n.right); }} class SearchNode{ int key; SearchNode left; SearchNode right; SearchNode(int n) {key=n;left=right=null;}} Aufgabe 72: Wir wollen in dieser Aufgabe die aus der Durchlaufordnung bestimmten Binarbaume explizit konstruieren (vergl. Aufgabe 3, Blatt 4). Dabei gehen wir von paarweise verschiedenen Elementen aus. (a) Gegeben sei die Levelorder. Schreiben Sie ein Programm Level, das den korrespondierenden Binarbaum konstruiert. Testen Sie Ihr Programm mit der Beispieleingabe 1 3 5 4 5 3 6 7 6 3 1 2 1. (b) Gegeben sei Preorder. Konstruieren Sie den korrespondierenden Suchbaum (!) in einem Programm Preorder. Testen Sie das Programm mit dem Durchlauf 12 6 2 1 4 3 5 8 7 9 10 11 14 13. Losung zur Aufgabe 72: Nicht vorhanden. Aufgabe 73: Das Spiel Nim ist ein beliebter Zeitvertreib. Gegeben sind dabei m Stapel von ni Holzern, 1 i m. Abwechselnd werden von den beiden Spielern von nur einem Stapel beliebig viele Holzer entnommen. Gewonnen ist das Spiel, falls man das letzte Holzchen wegnehmen kann. Als Beispiel betrachten wir m = 4 und die Eingabe [n1; n2; n3; n4] = [7; 5; 3; 1]: 276 KAPITEL 11. PRAXIS Die Strategie ist es, immer eine Gewinnposition zu erreichen, in der man bei einem beliebigen Zug des Gegners wieder eine Gewinnposition erreichen kann. Alle anderen Positionen sind Verlustpositionen. Nach einigen Spielversuchen stellt sich heraus, da neben [0; 0; 0; 0] einfache Gewinnpositionen durch [1; 1; 0; 0], [2; 2; 0; 0], [3; 3; 0; 0] oder auch durch [3; 2; 1; 0] gegeben sind. (a) Schreiben Sie Java-Programm, das eine 4-dimensionale Tabelle T fullt und bestimmt, ob die gegebene Anfangsstellung [7; 4; 3; 1] eine Gewinn- oder eine Verlustposition ist. Hier durfen Sie vier ineinander verschachtelte Schleifen verwenden. (b) Es gilt der folgende Satz: Eine Position ist genau dann eine Gewinnposition, wenn man die Binardarstellungen der Werte ni untereinander schreibt und zahlt, ob die Anzahl der Einsen in jeder Spalte gerade ist. Schreiben Sie ein Java-Programm, das mit Hilfe dieser Regel bei einer gegebenen Spielsituation den optimalen Zug ausfuhrt. Losung zur Aufgabe 73: class Node { public int p[4]; } class Nim { int T[8][6][4][2]; Nim() { T[0][0][0][0]=1; } public void dp(Node n) { int win = 1; for(int i=0;i<n.p[3];i++) win = win && T[n.p[0]][n.p[1]][n.p[2]][i]; for(int i=0;i<n.p[2];i++) win = win && T[n.p[0]][n.p[1]][i][n.p[3]]; for(int i=0;i<n.p[1];i++) win = win && T[n.p[0]][i][n.p[2]][n.p[3]]; for(int i=0;i<n.p[0];i++) win = win && T[i][n.p[1]][n.p[2]][n.p[3]]; T[n.p[0]][n.p[1]][n.p[2]][n.p[3]] = (1-win); } public void traverse(Node m) { Node n; for(int i0=0;i0<=m.p[0];i0++) for(int i1=0;i1<=m.p[1];i1++) for(int i2=0;i2<=m.p[2];i2++) for(int i3=0;i3<=m.p[3];i3++) if (i0+i1+i2+i3 >0) { n.p[0] = i0; n.p[1] = i1; n.p[2] = i2; n.p[3] = i3; dp(n); } } public static void main(String args[]) { Node m; Nim nim; m.p[0] = 7; m.p[1] = 5; m.p[2] = 3; m.p[3] = 1; nim.traverse(m); System.out.println(nim.T[7][5][3][1]); } } 277 Auszug aus StrategieZug im Java-Applet Nim-Spiel http://www.informatik.uni-freiburg.de/~hipke/NIM public static void ziehe(NIMSpiel spiel) { int xor=0; // Aktuellen XOR-Wert bestimmen for(int s=1;s<=spiel.anzahlStapel();s++) xor^=spiel.anzahl(s); if (xor==0) { NIMZufallZug.ziehe(spiel); return; } int maxbit=xor; // Bit am weitesten links bestimmen int shift=0; while(maxbit!=0) { maxbit>>=1; shift++; } maxbit=1<<(--shift); int stapelNr=1; // Stapel mit gesetztem maxbit suchen for(;stapelNr<=spiel.anzahlStapel();stapelNr++) if ((spiel.anzahl(stapelNr) & maxbit) != 0) break; int anzahl=spiel.anzahl(stapelNr); // Anzahl bestimmen int wegnehmen=anzahl-(anzahl^xor); spiel.nimmWeg(wegnehmen,stapelNr); } // Zug ausfuehren Aufgabe 74: Worterbucher bieten die Operation des Suchens, des Einfugens, und des Loschens eines Datensatzes aufgrund seines Schlussels an. AVL-Baume fuhren diese Operationen in logarithmischer Zeit zur Anzahl der Datenelemente durch. Wenn wir ganzzahlige Schlussel aus dem Intervall [0 : : : m] ablegen wollen, so konnen wir diese Operationen durch ein Array (Schubladen) von AVL-Baumen A0; : : : ; Ak 1 noch weiter beschleunigen. Hierbei wird der Schlussel i in den AVL-Baum mit Nummer (i mod k) gesucht, eingefugt oder geloscht. Implementieren sie die Klasse SchubladenAVL mit den Methoden insert, delete, search und print. Fur die Aufgabe verwende man die AVL-Implementation von Seite www.informatik.uni-freiburg.de/~edelkamp/info2/baeume/AVLTreeTest.java. Als Test wahle man k = 7, fuge die Zahlen von 0 bis 90 ein und losche die Zahlen von 21 bis 69 gema dem folgenden Schema. public class SchubladenTest { public static void main(String args[]) { System.out.println ("SchubladenTest"); System.out.println ("Erzeuge 7 Schubladen von AVL-Baeumen"); SchubladenAVL T = new SchubladenAVL (7); System.out.println ("Fuege Zahlen 0 .. 90 ein:"); for (int i=0; i<=90; ) T.insert (i++); System.out.println ("Loesche Zahlen 69 .. 21:"); KAPITEL 11. PRAXIS 278 for (int i=69; i>=21; ) T.delete (i--); System.out.println ("Ist 90 enthalten? "+T.search(90)); System.out.println ("Ist 21 enthalten? "+T.search(21)); } } Losung zur Aufgabe 74: import AVLTree; /* Hashtabelle fuer int-Zahlen mit Divisions-Rest-Methode und direkter Verkettung der Ueberlaeufer in AVL-Baeumen */ class SchubladenAVL { AVLTree harray[]; // Tabelle mit Referenzen auf AVL-Baeume SchubladenAVL (int length) { // Konstruktor harray = new AVLTree [length]; // Initialisieren der Tabelle for (int i = 0; i < length; i++) // mit leeren AVL-Baeumen harray [i] = new AVLTree (); } /* Einfuegen, Loeschen und Suchen zurueckfuehren auf AVL-Baeume */ boolean insert (int c) { return harray [c \% harray.length].insert (c); } boolean delete (int c) { return harray [c \% harray.length].delete (c); } boolean search (int c) { return harray [c \% harray.length].search (c); } void print () { // Ausgabe aller AVL-Baeume for (int i=0; i < harray.length; i++) { System.out.println ("AVL-Baum Nr. "+i+" (Hoehe "+ harray[i].height()+", interne Pfadlaenge "+harray [i].I()+"):"); harray [i].print (); } } } Aufgabe 75: In dieser Aufgabe sollen AVL-Baume mit den Java Graphik-Routinen gezeichnet werden. Als Beispiel dient die Abbildung 11.2. Erweitern Sie hierfur die im folgenden angegebene Klasse AVLTest. (Hilfreiche Programmtexte zur Bearbeiten der Aufgaben nden Sie unter www.informatik.uni-freiburg.de/~edelkamp/info2/baeume/visual). Um das Layout nicht allzu verkomplizieren, konnen Sie eine maximale Tiefe des AVLBaumes von drei oder vier annehmen. 279 Abbildung 11.2: AVL-Baum Visualisierung class AVLTest extends Frame { static AVLTree Baum; public AVLTest(){ //Konstruktor des Info-Fensters super("AVLTest"); resize(1000,270); show(); } public void update(Graphics g){paint(g);} public void paint(Graphics g){ // Zeichen des AVL-Baumes if (Baum==null) return; g.setColor(new Color(0,0,0)); g.fillRect(0,0,1000,500); g.setColor(new Color(255,255,255)); g.drawString("AVL-BAUM-VISUALISIERUNG (bis zur Tiefe 4)",20,40); g.setColor(new Color(255,0,0)); g.drawString("Balance-Werte",20,70); g.setColor(new Color(0,255,0)); g.drawString("Schluessel",20,85); ... // die Knoten zeichen } public void zeichneKnoten( // zeichnet einen AVL-Knoten AVLKnoten k,Graphics g,int x,int y, // x und y int vx,int vy) { // Vorgaenger x und y ... } 280 KAPITEL 11. PRAXIS Losung zur Aufgabe 75: public void zeichneKnoten (AVLKnoten k,Graphics g, int x,int y, int vx,int vy){ if (k==null) return; g.setColor(new Color(255,255,255)); g.drawOval(x-3,y,30,20); g.drawLine(x+10,y,vx+10,vy+20); g.setColor(new Color(0,255,0)); g.drawString(new Integer(k.key).toString(),x+5,y+13); g.setColor(new Color(255,0,0)); g.drawString(new Integer(k.b).toString(),x+5,y-7); } public void paint(Graphics g){ if (Baum==null) return; g.setColor(new Color(0,0,0)); g.fillRect(0,0,1000,500); g.setColor(new Color(255,255,255)); g.drawString("AVL-BAUM-VISUALISIERUNG (bis zur Tiefe 4)",20,40); g.setColor(new Color(255,0,0)); g.drawString("Balance-Werte",20,70); g.setColor(new Color(0,255,0)); g.drawString("Schluessel",20,85); //die Knoten zeichen: zeichneKnoten(Baum.Wurzel,g,500,50,500,30); zeichneKnoten(Baum.Wurzel.l,g,250,100,500,50); zeichneKnoten(Baum.Wurzel.r,g,750,100,500,50); if (Baum.Wurzel.l!=null){ 281 zeichneKnoten(Baum.Wurzel.l.l,g,125,150,250,100); zeichneKnoten(Baum.Wurzel.l.r,g,375,150,250,100); if (Baum.Wurzel.l.l!=null){ zeichneKnoten(Baum.Wurzel.l.l.l,g,75,200,125,150); zeichneKnoten(Baum.Wurzel.l.l.r,g,175,200,125,150); } if (Baum.Wurzel.l.r!=null){ zeichneKnoten(Baum.Wurzel.l.r.l,g,315,200,375,150); zeichneKnoten(Baum.Wurzel.l.r.r,g,415,200,375,150); } } if (Baum.Wurzel.r!=null){ zeichneKnoten(Baum.Wurzel.r.l,g,625,150,750,100); zeichneKnoten(Baum.Wurzel.r.r,g,875,150,750,100); if (Baum.Wurzel.r.l!=null){ zeichneKnoten(Baum.Wurzel.r.l.l,g,575,200,625,150); zeichneKnoten(Baum.Wurzel.r.l.r,g,675,200,625,150); } if (Baum.Wurzel.r.r!=null){ zeichneKnoten(Baum.Wurzel.r.r.l,g,815,200,875,150); zeichneKnoten(Baum.Wurzel.r.r.r,g,915,200,875,150); } } } Aufgabe 76: Programmieren Sie Brent's Algorithmus zum ezienten Hashing mit oener Adressierung als Klasse BrentOpenHashTable extends OpenHashTable. Implementieren Sie insbesondere die Funktion public void insert (Object key,Object value). Testen Sie Ihr Programm in der Klasse BrentOpenHash mit dem Standart-Array 19, 53, 72, 5, 12. Nutzen Sie dabei den Rahmen fur Hashtabellen aus der Vorlesung (im Netz zu nden). Losung zur Aufgabe 76: Losung korrespondiert zum alten Hashtabellenrahmen, (z.B. heit comparable jetzt Orderable, eigene Klassen werden nunmehr gro geschrieben u.a.) ist aber leicht zu ubertragen. Die Methode vertausche ist nicht sehr ubersichtlich. Besser ist die direkte Einbeziehung der Funktion in insert. class brentOpenHashTable extends openHashTable { brentOpenHashTable (int capacity) { KAPITEL 11. PRAXIS 282 super (capacity); } protected int hprime (Object key) { return 1+(key.hashCode()\%(capacity-2)); } protected int s (int j, Object key) { return j * hprime (key); } protected void vertausche (openTableEntry entry1, openTableEntry entry2) { Object tempItem = entry1.item; entry1.item = entry2.item; entry2.item = tempItem; Object tempKey = entry1.key; entry1.key = entry2.key; entry2.key = tempKey; } public void insert (Object key, Object item) { openTableEntry newEntry = new openTableEntry (key,item); int i = h(key); System.out.print("Betrachtete Speicherplaetze: ("+ i+ "," + key.hashCode ()+")"); while (hashEntry[i].marke == BELEGT) { int i1 = (i-s(1,newEntry.key))\%capacity; if (i1 < 0) i1 = i1+capacity; int i2 = (i-s(1,hashEntry[i].key))\%capacity; if (i2 < 0) i2 = i2+capacity; if ((hashEntry[i1].marke == BELEGT) && (hashEntry[i2].marke != BELEGT)) { vertausche(hashEntry[i], newEntry); i = i2; } else i = i1; System.out.print(" (" + i+ "," + newEntry.key.hashCode ()+")"); } System.out.println (); hashEntry[i].key = newEntry.key; hashEntry[i].item=newEntry.item; hashEntry[i].marke = BELEGT; } 283 } public class brentOpenHash { // Programm zum Testen von Hashverfahren public static void main(String args[]){ int vec[] = {19, 53, 72, 5, 12}; // "Standard"-Array if (args.length != 0) { vec = new int [args.length]; for (int j = 0 ; j < args.length ; j++) vec[j] = Integer.valueOf(args[j]).intValue(); } Integer [] t = new Integer [vec.length]; for (int i = 0; i <= vec.length - 1; i++) { t[i] = new Integer(vec[i]);System.out.print(vec[i] + " "); } System.out.println(); brentOpenHashTable h = new brentOpenHashTable (7); h.printTable (); for (int i = 0; i <= t.length - 1; i++) { h.insert(t[i], t[i]); h.printTable (); } h.delete(t[0]); h.printTable(); System.out.println(h.search(t[4]).hashCode ()); h.delete(t[3]); h.printTable(); h.insert(new Integer (54), new Integer (54)); h.printTable (); } } Aufgabe 77: In der Vorlesung wurden unterschiedliche Hashverfahren besprochen. Programmieren Sie jeweils eine Klasse fur die angegebene Methode und testen Sie ihre Programme mit verschiedenen Eingabefolgen. Nutzen Sie auch hier den Rahmen aus der Vorlesung. (a) Die multiplikative Methode wahlt eine irrationale Zahl zwischen Null und Eins aus, multipliziert den Schlussel mit dieser Zahl und entnimmt die Nachkommastellen. Diese werden wiederum mit der Tabellengroe multipliziert und letztendlich der p Nachkommabereich weggestrichen. Wahlen Sie fur den goldenen Schnitt ( 5 1)=2. 284 KAPITEL 11. PRAXIS (b) Universelles Hashing umgeht den worst-case im Hashing, der sich durch eine ungunstige Eingabe immer ergeben kann, mit Randomisierung. In der Vorlesung wurde gezeigt, da die Klasse H = f((ax + b) mod p) mod m mit 1 a < p und 0 b < pg fur eine Primzahl p universell ist. Dabei sei m die Groe der Hashtabelle und das Universum aller Schlussel bestehe o.B.d.A. aus p Elementen. Losung zur Aufgabe 77: public class MultiOpenHash extends OpenHashTable{ public MultiOpenHash () { super (); } public MultiOpenHash (int capacity) { super(capacity); } public int h (Object key) { double teta = (Math.sqrt(5)-1)/2; double hk= (key.hashCode()*teta)\%1; int hashValue= (int) (getCapacity ()*hk); return hashValue; } } public class MultiOpenHashTest { public static void main(String args[]){ int vec[] = {19, 53, 72, 5, 12}; System.out.println(); if (args.length != 0) { vec = new int [args.length]; for (int j = 0 ; j < args.length ; j++){ vec [j] = Integer.valueOf(args[j]).intValue(); } } System.out.println(); Integer [] t = new Integer[vec.length]; for (int i = 0; i <= vec.length - 1; i++) { t[i] = new Integer(vec[i]); System.out.print(vec[i] + " "); } System.out.println(); MultiOpenHash h = new MultiOpenHash (7); for (int i = 0; i <= t.length - 1; i++) { h.insert(t[i], null); h.printTable (); 285 } } } public class UniOpenHash extends OpenHashTable { boolean init=true; int a[]; int b[]; int p=11; //Primzahl public UniOpenHash () { super (); } public UniOpenHash (int capacity) { super(capacity);} public int h (Object key) { if(init){ init=false; a=new int[p]; b=new int[p]; Random zufall=new Random(); for(int i=0;i<p;i++){ a[i]=(int) (p*(zufall.nextDouble()\%1)); //% bedeutet b[i]=(int) (p*(zufall.nextDouble()\%1)); }}//mod (Modulo) int hashValue=((a[key.hashCode()\%p]*key.hashCode()+ b[key.hashCode()\%p])\%p)\%getCapacity (); if (hashValue < 0) hashValue = hashValue + getCapacity (); return hashValue; }} public class UniOpenHashTest { public static void main(String args[]){ int vec[] = {19, 53, 72, 5, 12}; System.out.println(); if (args.length != 0) { vec = new int [args.length]; for (int j = 0 ; j < args.length ; j++) { vec [j] = Integer.valueOf(args[j]).intValue(); } } Integer [] t = new Integer[vec.length]; for (int i = 0; i <= vec.length - 1; i++) { t[i] = new Integer(vec[i]); System.out.print(vec[i] + " "); KAPITEL 11. PRAXIS 286 } System.out.println(); UniOpenHash h = new UniOpenHash (7); for (int i = 0; i <= t.length - 1; i++) { h.insert(t[i], null); h.printTable (); } } } Aufgabe 78: Schweden- bzw. Kreuzwortratsel sind ein beliebter Zeitvertreib. Die automatische Generierung dieser Ratsel eine echte Herausforderung. Anbei ein Gerust, das das Problem mittels Backtracking lost. Programm und Wortschatz nden Sie im Vorlesungsverzeichnis im Netz. Leider ist das Programm noch zu langsam. (a) Die Reihenfolge der Wortbelegungen ist entscheidend. Programmieren Sie eine Methode permute, die das Array All mischt, und nden Sie eine Loesung durch Neustarts. (b) Nutzen Sie Hashing, um die Suchzeit im Wortschatz (Methode fits) zu verringern. Suchen Sie dabei z.B. alle Worter der Lange k, die an Stelle j den Buchstaben l haben. Die Tabelle wird recht gro, da fur jeden Buchstaben ein Eintrag abgelegt wird. Konzentrieren Sie sich deshalb auf Indizes zu den Wortern und Teilen des Wortschatzes, die durch das festverdrahtete Ratsel auch tatsachlich angefragt werden. Es bietet sich eine Partitionierung der Worter nach Ihrer Lange an. (c) Entwickeln Sie eigene Ideen und versuchen Sie sich an groeren Ratseln (ein weiteres Beispiel ist im Netz zu nden). Die besten Abgaben werden pramiert. import java.io.*; class Word { int xpos, ypos, length; public String st; public boolean hor; Word(int x,int y,int l) class CrossWord { String St[]; Word All[]; // // // { Koordinaten und Laenge des einzusetzenden Wortes einzusetzendes Wort horizontale oder vertikale Ausrichtung xpos=x; ypos=y; length=l; st = ""; }} // Array aller Woerter im Wortschatz // Array aller einzusetzenden Woerter 287 char Board [][]; // Darstellung des Kreuzwortraetsels static final int rows = 6, cols = 6, size=25017; Word Hors[] = { new Word(1,0,6), new Word(2,1,3), new Word(3,0,5), new Word(4,3,3), new Word(5,0,3) }; Word Verts[] = { new Word(1,1,5), new Word(1,2,3), new Word(0,3,5), new Word(3,4,3), new Word(0,5,3)}; CrossWord() { St=new String[size]; All=new Word[Hors.length+Verts.length]; Board=new char[rows][cols]; int line=0,k=0; try { System.out.println("Reading File"); // Einlesen des Wortschatzes FileReader f = new FileReader("in"+""); StreamTokenizer tok=new StreamTokenizer(f); while (tok.nextToken() != tok.TT_EOF) St[line++] = tok.sval; f.close(); } catch(IOException e) { System.err.println(e); System.exit(1); } System.out.println("Done."); for(int i=0;i<Hors.length;i++) for(int j=0;j<Hors[i].length;j++) Board[Hors[i].xpos][Hors[i].ypos+j]=' '; for(int i=0;i<Verts.length;i++) for(int j=0;j<Verts[i].length;j++) Board[Verts[i].xpos+j][Verts[i].ypos]=' '; for(int i=0;i<Hors.length;i++) { All[k] = Hors[i]; All[k++].hor = true; } for(int i=0;i<Verts.length;i++) { All[k] = Verts[i]; All[k++].hor = false; } } private void set() { // plaziere alle gefunden Woerter in Raetsel for(int i=0;i<rows;i++) for(int j=0;j<cols;j++) Board[i][j] = '#'; for(int i=0;i<Hors.length;i++) for(int j=0;j<Hors[i].length;j++) if (Hors[i].st != "") Board[Hors[i].xpos][Hors[i].ypos+j] = Hors[i].st.charAt(j); else if (Board[Hors[i].xpos][Hors[i].ypos+j] == '#') Board[Hors[i].xpos][Hors[i].ypos+j] = ' '; 288 KAPITEL 11. PRAXIS for(int i=0;i<Verts.length;i++) for(int j=0;j<Verts[i].length;j++) if (Verts[i].st != "") Board[Verts[i].xpos+j][Verts[i].ypos] = Verts[i].st.charAt(j); else if (Board[Verts[i].xpos+j][Verts[i].ypos] == '#') Board[Verts[i].xpos+j][Verts[i].ypos] = ' '; } private String get(Word w) { // entnehme Muster in derzeitigen Raetsel char c[] = new char[w.length]; // erzeuge Zeichenkette for(int j=0;j<w.length;j++) if (w.hor==true) c[j]=Board[w.xpos][w.ypos+j]; else c[j]=Board[w.xpos+j][w.ypos]; return new String(c); } // gib Ergebnis zurueck private int fits(Word w, int start) { // pruefe ob Wort in Wortschatz passt String pat = get(w); int j; // Hole das Muster fuer Wort w for (int i=start;i<size;i++) if (pat.length() == St[i].length()) { // Laenge stimmt ueberein for(j=0;j<pat.length();j++) if (pat.charAt(j) != ' ' && pat.charAt(j) != St[i].charAt(j)) break; if (j == pat.length()) return i; } // Es gibt ein Wort fuer Muster return -1; } // Kein Wort fuer Muster vorhanden private boolean backtrack(int i) { // Suche durch Backtracking if (i == All.length) return true; // Abbruch, da Raetsel geloest int found=-1; // Position eines gefundenen Wortes im Wortschatz while ((found=fits(All[i],found+1)) != -1) { // solange naechstes Wort ex. All[i].st = St[found]; set(); i++; // Einfuegen des gefundenen Wortes if (backtrack(i)) return true; // rekursiver Aufruf i--; All[i].st = ""; set(); } // Entfernen des gefunden Wortes return false; } // Abbruch, kein passendes Wort gefunden public boolean search() { return backtrack(0); } // Schnittstelle public String toString() { StringBuffer s = new StringBuffer(); // Ausgabe des Kreuzwortraetsels for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++) s.append(Board[i][j]); s.append("\n");} return s.toString(); } } 289 class Cross { // Haupt- bzw. Testklasse public static void main(String args[]) { CrossWord C = new CrossWord(); if (C.search()) System.out.println(C); } } Losung zur Aufgabe 78: private void permute() { for(int i=0;i< All.length;i++) { int r = (int) (Math.random() * All.length); Word temp = All[r]; All[r] = All[i]; All[i] = temp; } } Aufgabe 79: Ein Wurfelwusel ist aus gleichartigen Wurfeln zusammengesetzt, welche auf geheimnisvolle Weise aneinander haften. Der Wusel bendet sich entweder in Ruhe oder in Bewegung. Er besteht aber immer aus einem Stuck. In Ruhe haften immer jeweils ganze Wurfelachen aufeinander. In Bewegung wird eine Gruppe von Wurfeln (Anzahl 1) in Richtung einer der Wurfelachsen um eine Wurfellange bewegt. Mogliche Bewegungsrichtungen sind also links, rechts, auf, ab, vor und zuruck. Wahrend einer Bewegung haftet die verschobene Gruppe immer mit mindestens der Groe einer Wurfelache am Rest. Mogliche Bewegungen: Wurfel, die die Standache beruhren, konnen nur aufwarts bewegt werden. (Ein Wurfelwusel hebt die Fue beim Laufen namlich an und schlurft nicht uber den Boden.) Wenigstens ein Wurfel mu die Standache beruhren, wobei der Wusel nicht an der Standache haftet. Die ubrigen Wurfel mussen so verteilt sein, da der Wusel nicht umkippt. Beispielwusel: 290 KAPITEL 11. PRAXIS Zur Statik des Wurfelwusels: Der Schwerpunkt des Wusels lat sich leicht berechnen. Der Wusel fallt um, wenn der Schwerpunkt { bzw. die Projektion des Schwerpunkts auf den Boden { sich auerhalb der durch die Beine denierten Grundache bendet. Ziel ist es, von einer gegebenen Anfangsstellung heraus in eine vordenierte Zielposition zu gelangen. Weiterhin mussen dabei unter Umstanden kleine Hindernisse uberwunden werden konnen. Uns ist bewut, da das gestellte Problem eine harte Nu ist und in einem einfachen Ansatz fur groere Wusel nicht immer zu losen ist. Hier sollen Ihre Verbesserungen greifen. Index EXTERNAL-HEAPSORT, 89 WEAK-HEAPSORT, 84 Allgemeine Sortierverfahren, 48 Alternative mit Zeigern, 29 Amortisierung, 116 Analyse, 80, 97, 110 Analyse der direkten Verkettung, 160 Analyse ideales Hashing, 168 Analyse von Mergesort, 57 Analyse von Quicksort, 62 Analyse von Radix-exchange-sort, 93 Anwendung: Topologische Sortierung, 42 Arraydarstellung, 31 Arrayeinbettung, 86 Arrays, 28 Aufgaben, 17 Aufrufbaum, 38 Aufteilen (2-3 Baum), 140 Auswahlsort, 52 Average Case, 64 Average Case Analyse Quicksort, 70 Beispiel, 15, 121, 155, 170, 173 Beispiel zu Shellsort, 54 Beispiel:, 43 Beispiel: Ordered double Hashing, 174 Beispiel: Tower-of-Hanoi Problem, 36 Beispiele, 22 Bemerkungen, 16 Bottom-up-Heapsort, 81 Bottom-Up-Heapsort: Implementation, 82 Clever-Quicksort, 73 Das Auswahlproblem, 100 Das Maxsummenproblem, 22 Das Sortierproblem, 48 Der clevere Algorithmus, 23 Der normale Algorithmus, 22 Divide-And-Conquer, 23 Doppelte Verkettung, 34 Double Hashing, 170 Durchlaufreihenfolgen, 125, 130 Dynamisch, 32 Eigenschaften, 130, 143 Elementare Sortierverfahren, 49 Entfernen, 133 Entfernen des Maximums, 76 Ergebnisse, 83 Erstellen eines Heaps, 78 Exponentielle Algorithmen, 20 Exponentielle Suche, 114 Fallunterscheidung, 14 Fibonacci-Suche, 112 Frequency Count, 116 Funktionenklassen, 16 Generierung eines Weak-Heaps, 85 Geschlossene Hashverfahren, 150 Graphen, 178 Grundlegende Implementation, 50 Grundtypen und Knoten, 30 Hashfunktion, 152 291 292 Hashverfahren, 150, 152 Hashverfahren|Beispiele, 150 Heapsort, 75 Implementation, 59, 73, 87, 112, 125, 131, 134, 178 Implementation (Rahmen), 100 Implementation Adjazenzliste, 180 Implementation Adjazenzmatrix, 179 Implementation in Java, 153, 158 Implementation: Aufteilungsschritt, 61 Implementation: Decomposable, 94 Implementation: Fachverteilung, 98 Implementation: Ganzzahlen, 94 Implementation: Oene Hashtabelle, 163 Implementation: Radix-Exchange, 95 Implementation: Shellsort, 54 Implementation: Suchen, 175 Implementation: Zeichenketten, 95 Implementation: QUICK-HEAPSORT, 90 Implementationen, 29 Interpolationssuche, 115 Iteriertes Versickern, 80 Kriterien, 163 Landau'sche O-Notation, 16 Laufzeitanalyse, 88 Leistungsverhalten von Algorithmen, 14 Lineares Sondieren, 167 Listen, 29 Maximaler Zeitaufwand cost(A) in Java, 21 Median-of-Median, 102 Mengen, 45 Mergesort, 56 Messung des Leistungsverhaltens, 14 Motivation, 27 INDEX Move-To-Front Analyse, 118 Multiplikative Methode, 154 Oene Hashverfahren, 162 Operationen, 41 Optimale Nutzung der Vorsortierung, 104 Probleme, 151 Problemstellung, 108 Quadratisches Sondieren, 167 Quick-Heapsort, 89 Quicksort, 59 Radix-exchange-sort, 92 Radixsort, 92 Reines 2-Wege-Mergesort, 58 Rotationen, 135 Schlange/Queue:, 41 Selbstanordnende lineare Listen, 116 Shellsort, 54 Situation im Oenen Hashing, 162 Skalierbarkeiten, 19 Sondierungsfolgen, 162 Sortieren durch Fachverteilung, 97 Sortieren vorsortierter Daten, 103 Sortierungsphase, 86 Stapel/Stack, 36 State-of-the-Art, 69 Suchen, 164 Test-Programm, 159, 166 Testen: Tower-of-Hanoi-Stack, 40 Tiefe, 138 Tiefensuche, 181 Topologische Sortierung, 181 Uniformes Sondieren, 168 Universelles Hashing, 155, 156 Veranschaulichung, 76 INDEX Verbesserung der erfolglosen Suche, 174 Verbesserung der erfolgreichen Suche, 171, 172 Verfeinerung, 32 Verschmelzen, 84 Verschmelzen zweier Teilfolgen, 56 Versickern, 77 Verwendete Datenstrukturen, 43 Visualisierung, 17 Visualisierungen, 33 Wahl der Hash-Funktion, 153 Wahl des Pivotelements, 102 Weitere Eigenschaften, 18 Zusammestellung der Ergebnisse, 24 293