Aufgabe 3 a) Wir verwenden zur Lösung den Algorithmus Build-Heap 1, dieser verwendet die Funktion Heapify. Unser Array A ist gegeben durch [7, 10,, 5, 5,, 3, 3, 17]. 10 5 5 3 17 7 Abbildung 1: Das Array A als Baum (vgl. Foliensatz 16, Folie 3) In Heapify wird nach Aufruf mit dem Array A die Variable heap-size auf 9 gesetzt. Unsere for-schleife läuft also von 9 = 4 bis 1. i=4 Aufruf von Heapify(A,4) i = 4 A[i] = 5 l = left(i) = i = A[l] = 3 r = right(i) = i + 1 = 9 A[r] = 17 In Zeilen 3 und 4 von Heapify wird der Index des größeren Werts von A[l] und A[i] bestimmt und in der Variable largest gespeichert, wenn l heap-size[a] gilt. Das ist hier der Fall, also bekommt largest den Wert von l = zugewiesen. Es gilt A[largest] = 3. In Zeile 5 von Heapify wird der Wert von A[largest] mit dem Wert A[r] verglichen, wenn r heap-size[a] gilt. Diese Bedingung ist erfüllt, A[largest] ist aber größer als A[r], also bleibt der Wert von der Variable largest unverändert. Die Bedingung in Zeile 6 ist erfüllt, deshalb werden die Werte von A[i] und A[largest] vertauscht. Der resultierende Baum ist: 1 siehe Foliensatz 16, Folie 36 siehe Foliensatz 16, Folie 14ff.
10 3 5 5 17 7 Nachdem die Werte vertauscht wurden, wird Heapify(A,) aufgerufen. Aufruf von Heapify(A,) i = A[i] = 5 l = left(i) = i = 16 r = right(i) = i + 1 = 17 In Zeilen 3 und 4 wird largest auf den Wert i = gesetzt, da l > heap-size[a] gilt. In Zeile 5 wird keine Änderung an der Variable largest vorgenommen, da r > heap-size[a] ist. Jetzt gilt i = largest und somit wird der Aufruf von Heapify(A,) ohne Änderungen oder weiteren Aufrufen von Heapify beendet. i=3 Aufruf von Heapify(A,3) i = 3 A[i] = l = left(i) = i = 6 A[l] = r = right(i) = i + 1 = 7 A[r] = 3 Das Element A[i] ist größer als seine beiden Kinder A[l] und A[r], also ist largest = i und es werden keine Änderungen und auch kein weiterer Aufruf von Heapify vorgenommen. i= Aufruf von Heapify(A,) i = A[i] = 10 l = left(i) = i = 4 A[l] = 5 r = right(i) = i + 1 = 5 A[r] = 5 Es gilt l heap-size[a] A[l] A[i] Also wird in Zeilen 3 und 4 largest = i = gesetzt. Weiterhin gilt r heap-size[a] A[r] > A[largest] Also wird in Zeile 5 largest = r = 5 gesetzt. Da largest i ist wird A[i] mit A[largest] vertauscht und Heapify(A,5) aufgerufen.
Aufruf von Heapify(A,5) 5 3 10 5 17 7 i = 5 A[i] = 10 l = left(i) = i = 10 r = right(i) = i + 1 = 11 In Zeilen 3 und 4 wird largest auf den Wert i = 5 gesetzt, da l > heap-size[a] gilt. In Zeile 5 wird keine Änderung an der Variable largest vorgenommen, da r > heap-size[a] ist. Jetzt gilt i = largest und somit wird der Aufruf von Heapify(A,5) ohne Änderungen oder weiteren Aufrufen von Heapify beendet. i=1 Aufruf von Heapify(A,1) i = 1 A[i] = 7 l = left(i) = i = A[l] = 5 r = right(i) = i + 1 = 3 A[r] = Es gilt l heap-size[a] A[l] > A[i] Also wird largest = l = gesetzt. Weiterhin gilt r heap-size[a] A[r] A[largest] Der Wert der Variable largest wird also nicht verändert. Da largest i ist wird A[i] mit A[largest] vertauscht und Heapify(A,) aufgerufen. 7 3 10 5 17 5
Aufruf von Heapify(A,) i = A[i] = 7 l = left(i) = i = 4 A[l] = 3 r = right(i) = i + 1 = 5 A[r] = 10 Es gilt l heap-size[a] A[l] > A[i] Also wird largest = l = 4 gesetzt. Weiterhin gilt r heap-size[a] A[r] A[largest] Der Wert der Variable largest wird also nicht verändert. Da largest i ist wird A[i] mit A[largest] vertauscht und Heapify(A,4) aufgerufen. Aufruf von Heapify(A,4) 3 7 10 5 17 5 i = 4 A[i] = 7 l = left(i) = i = A[l] = 5 r = right(i) = i + 1 = 9 A[r] = 17 Es gilt l heap-size[a] A[l] A[i] Also wird largest = i = 4 gesetzt. Weiterhin gilt r heap-size[a] A[r] > A[largest] Der Wert der Variable largest wird also auf r = 9 gesetzt. Da largest i ist wird A[i] mit A[largest] vertauscht und Heapify(A,9) aufgerufen. 17 5 7 3 5 10
Aufruf von Heapify(A,9) i = 9 A[i] = 5 l = left(i) = i = 1 r = right(i) = i + 1 = 19 In Zeilen 3 und 4 wird largest auf den Wert i = 9 gesetzt, da l > heap-size[a] gilt. In Zeile 5 wird keine Änderung an der Variable largest vorgenommen, da r > heap-size[a] ist. Jetzt gilt i = largest und somit wird der Aufruf von Heapify(A,9) ohne Änderungen oder weiteren Aufrufen von Heapify beendet. Nach diesem Durchlauf ist Build-Heap fertig. Der entstehende Heap ist in Abbildung zu sehen, es gilt: A = [5, 3,, 17, 10,, 3, 4, 5]. b) Sei A ein max-heap. Das bedeutet, dass in A die max-heap-eigenschaft gilt: Für jeden Knoten i außer der Wurzel gilt A[P arent(i)] A[i]. Sei i der Index einer Wurzel eines Teilbaumes. Sei i ein Kind von i und sei weiterhin A[i ] > A[i ]. Das wäre aber eine Verletzung der max-heap-eigenschaft, es gilt jetzt nämlich nicht mehr für alle Knoten, dass A[P arent(i)] A[i] gilt. Das ist ein Widerspruch zur Annahme, dass A ein max-heap ist.
Aufgabe 4 a) Diese Aussage ist richtig. Beweis: Ein Array A ist aufsteigend sortiert genau dann, wenn gilt: A[i] A[i + 1] 1 i < n (1) Ein Array A erfüllt die min-heap-eigenschaft genau dann, wenn gilt: A[P arent(i)] A[i] Benutzt man left(x) und right(x) und setzt die entsprechenden Definitionen ein, so erhält man folgende Schreibweise der min-heap-eigenschaft: A[i] A[left(i)] A[i] A[i] () A[i] A[right(i)] A[i] A[i + 1] (3) Wir wollen nun zeigen, dass ein Array, dass aufsteigend sortiert ist auch die min-heap- Eigenschaft erfüllt: Array aufsteigend sortiert Array erfüllt min-heap-eigenschaft Aus A[i] A[i + 1] folgt sofort, dass A[i] A[i] und A[i] A[i + 1] auch gelten. b) Diese Aussage ist falsch. Gegenbeispiel: 1 3 5 4 Dies ist offensichtlich ein min-heap, für jeden Knoten i außer der Wurzel gilt A[P arent(i)] A[i]. Betrachtet man das Array A = [1, 3,, 5, 4], so sieht man, dass es nicht aufsteigend sortiert ist. Damit wurde die Aussage widerlegt.
Aufgabe 5 1 function k k l e i n s t e Elemente (A, k ) // Erzeuge Array mit k Elementen, d i e s e s Array 3 // wird a l s max heap g e p f l e g t 4 B = new array [ 1,.., k ] 5 for i 1 to k do 6 B[i] 7 end for 9 for i 1 to n do 10 i f A[i] < B[1] then 11 B[1] A[i] 1 // s t e l l e max Heap E i g e n s c h a f t wieder her 13 Heapify(B, 1) 14 end i f 15 end for 16 17 // s o r t i e r e B a u f s t e i g e n d 1 MergeSort(B, 1, k) 19 0 return B 1 end Laufzeit Analyse: Die Zeilen 4 und 5-7 können in Laufzeit O(k) ausgeführt werden. Der Aufruf Heapify(B,1) benötigt Zeit O(log k), die Funktion wird im worst-case n mal aufgerufen. Für Zeilen 9-15 ergibt sich also eine Laufzeit von O(n log k). Der Aufruf von MergeSort benötigt Zeit O(k log k) da hier k Elemente sortiert werden sollen. Da n k gilt, hat der Algorithmus k-kleinste-elemente Laufzeit O(n log k).
Aufgabe 6 a) Die rot markierten Elemente wurden bereits durch den Algorithmus bearbeitet, die unterstrichenen Werte für i beziehen sich auf die Werte innerhalb der Funktion Max-Heap- Insert. i= Max-Heap-Insert(A, A[]) i= 10 5 5 3 17 7 Parent() = = 1 i = > 1 A[1] < A[] Also müssen die Elemente A[1] und A[] vertauscht werden, i wird auf Parent() gesetzt, also i = 1. 7 5 5 3 17 10 i=1 i ist jetzt 1, die while-schleife wird nicht weiter ausgeführt. i=3 Max-Heap-Insert(A, A[3]) i=3 7 5 5 3 17 Parent(3) = 10 3 = 1 i = 3 > 1 A[1] A[3]
Die Schleife wird also nicht betreten. i=4 Max-Heap-Insert(A, A[4]) 7 5 5 3 17 10 i=4 Parent(4) = 4 = i = 4 > 1 A[] A[4] Die Schleife wird also nicht betreten. i=5 Max-Heap-Insert(A, A[5]) 7 5 5 3 17 10 i=5 Parent(5) = 5 = i = 5 > 1 A[] < A[5] Also müssen die Elemente A[] und A[5] vertauscht werden, i wird auf Parent(5) gesetzt, also i =. 10 5 5 7 3 17
i= Parent() = = 1 i = > 1 A[1] < A[] Also müssen die Elemente A[1] und A[] vertauscht werden, i wird auf Parent() gesetzt, also i = 1. 5 10 5 7 3 17 i=1 i ist jetzt 1, die while-schleife wird nicht weiter ausgeführt. i=6 Max-Heap-Insert(A, A[6]) 5 10 5 7 3 17 i=6 Parent(6) = 6 = 3 i = 6 > 1 A[3] A[6] Die Schleife wird also nicht betreten. 5 10 5 7 3 17
i=7 Max-Heap-Insert(A, A[7]) i=7 Parent(7) = Die Schleife wird also nicht betreten. 7 = 3 i = 7 > 1 A[3] A[7] i= Max-Heap-Insert(A, A[]) i= 5 10 5 7 3 17 Parent() = = 4 i = > 1 A[4] < A[] Also müssen die Elemente A[4] und A[] vertauscht werden, i wird auf Parent() gesetzt, also i = 4. i=4 5 10 3 7 5 17 Parent(4) = 4 = i = 4 > 1 A[] < A[4] Also müssen die Elemente A[] und A[4] vertauscht werden, i wird auf Parent(4) gesetzt, also i =. 5 3 10 7 5 17
i= Parent() = = 1 i = > 1 A[1] A[] Die Schleife wird also nicht betreten. i=9 Max-Heap-Insert(A, A[9]) i=9 5 3 10 7 5 17 Parent(9) = 9 = 4 i = 9 > 1 A[4] < A[9] Also müssen die Elemente A[4] und A[9] vertauscht werden, i wird auf Parent(9) gesetzt, also i = 4. i=4 5 3 17 7 5 10 Parent(4) = 4 = i = 4 > 1 A[] A[4] Die Schleife wird also nicht betreten. 5 3 17 7 5 10
b) Die beiden Algorithmen erzeugen unterschiedliche Heaps, das Gegenbeispiel wurde in Aufgaben 3.a und 6.a geliefert. c) Korrektheit von Max-Heap-Insert Invariante Vor dem Durchlauf der while-schleife mit Index i erfüllt der Teilbaum mit Wurzel A[i] die max-heap-eigenschaft bis auf eine Mögliche Verletzung: A[i] kann größer sein als A[P arent(i)]. Initialisierung Vor dem ersten Schleifendurchlauf wurde nur der Wert von A[i] geändert und von daher kann nur dieses Element die max-heap-eigenschaft verletzen. Erhaltung In der while-schleife wird eine eventuelle auftretende Verletzung der maxheap-eigenschaft in der Zeile 5 behoben. Jetzt gilt die max-heap-eigenschaft im Teilbaum mit Wurzel A[i]. Durch die Vertauschung kann nur die max-heap-eigenschaft zwischen i und P arent(i) verletzt werden. Terminierung Erreicht der Algorithmus die Wurzel des Heaps (i = 1), so terminiert er und das gesamte Array erfüllt die max-heap-eigenschaft. Korrektheit von Build-Max-Heap Invariante Vor dem Durchlauf der Schleife mit Index i ist das Array A[1,.., i 1] ein max-heap Initialisierung Der erste Durchlauf erfolgt mit i =. Das Array A[1,.., 1] ist ein maxheap Erhaltung Vor Durchlauf mit Index i gilt die Invariante. Es wird dann Max-Heap- Insert(A, A[i]) aufgerufen. Durch diesen Aufruf wird der Heap korrekt um den Wert A[i] ergänzt. Die Korrektheit folgt aus der Korrektheit von Max-Heap-Insert. Terminierung Vor Durchlauf mit Index i = length(a) + 1 ist A[1,.., length(a)] ein max-heap. Laufzeit Die Funktion Build-Max-Heap (A) ruft in der for-schleife n 1 mal den Algorithmus Max-Heap-Insert auf. In diesem Algorithmus wird in der while-schleife im worst-case der Baum von einem Blatt zur Wurzel durchlaufen. Da es sich hier im weitesten Sinne
um einen vollständigen Binärbaum handelt ist die Höhe des Baums log n. Wir haben also insgesamt eine Laufzeit von n }{{ 1 } O(log n) }{{} = O(n log n) Aufrufe von Max-Heap-Insert Laufzeit von Max-Heap-Insert