Klausur „Diskrete Mathematik II“ Musterlösung zu den Vorbereitungsaufgaben 1 Aufgabe 1: Umkehren einer Liste (Pseudocode auf hohem Niveau) Eingabe: Eine einfach verkettete Liste Ausgabe: Eine einfach verkettete Liste, die die Elemente er Eingabeliste in umgekehrter Reihenfolge enthält Programm: Durchlaufe die Eingabeliste beginnend mit dem Kopf. Jedes Element wird kopiert, und die Kopie wird jeweils vorne (d.h. an der Kopfseite) in eine neue Liste eingehängt. Der Kopf der neuen Liste ist das zuletzt eingehängte Element. 2 Aufgabe 1: Umkehren einer Liste (Alternative, eher an Java angelehnt) // Die folgende Lösung verwendet die Klasse "Element" aus der // Vorlesung. Die Lösung mit "Element" und "Liste" ist analog. Element Kopf = ...; //Kopf der zu invertierenden Liste Element aktuell = Kopf; Element neuerKopf = NULL; //Kopf der invertierten Liste while( aktuell != NULL ) { Element Neu = new Element(aktuell.Wert); //Erzeugen einer Kopie Neu.Weiter = neuerKopf; neuerKopf = Neu; aktuell = aktuell.Weiter; } 3 Aufgabe 2: Multiplikation einer Matrix mit einem Vektor int n = ...; //Länge des Vektors bzw. Anzahl der Spalten int m = ...; // Anzahl der Zeilen double [ ] vek = new double[n]; // vek ist der Vektor double [ ][ ] mat = new double[m,n]; // mat ist die Matrix double [] ergebnis = new double [m] ; ergebnis = {0,0,0...} //Produkt von vek und mat //Initialisierung mit 0 vek = ...; mat = ...; // Belegung von vek und mat for ( int i = 1 ; i <= m ; i++ ) for ( int j = 1 ; j <= n ; j++ ) { ergebnis[i] = ergebnis[i] + mat[i,j] * vek[j]; } 4 Aufgabe 3: Umgekehrte Polnische Notation Prozedur zur Erzeugung der Umgekehrten Polnischen Notation aus einem Syntaxbaum: upn() { upn(wurzel); } upn(Knoten W) { if(W = NULL) return; print(W); print("("); upn(W.LinkerNachfolger); print(","); upn(W.RechterNachfolger); print(")"); } Bemerkung: Die Prozedur "upn()" ist fast identisch zu der Prozedur "PreOrder()" (Mathe 8, 1. Semester). Dort fehlen nur die Befehle zur Ausgabe der Klammern und des Komma (print("("); print(","); print(")")). 5 Aufgabe 4.1: Algorithmus zum Entfernen eines Knotens aus einem binären Suchbaum: Durchsuche den Baum solange, bis der zu löschende Knoten gefunden ist (z.B. mit Breiten- oder Tiefensuche); dieser sei L; merke Dir den Zeiger auf L vom Vorgänger im Baum; dieser sei zL • falls L keine Nachfolger hat, lösche L, setzt zL auf Null und beende die Prozedur andernfalls suche den Ersatzknoten E für L wie folgt: • falls L einen linken Nachfolger hat, gehe einmal nach links und dann solange nach rechts, wie rechte Nachfolger vorhanden sind. Der gefundene Knoten sei E. • falls L keinen linken Nachfolger hat, gehe einmal nach rechts und dann solange nach links, wie linke Nachfolger vorhanden sind. Der gefundene Knoten sei E. ersetze L durch E: dazu wird zL auf E gesetzt, und der linke und rechte Nachfolgen von L wird dem linken und rechten Nachfolger von E zugewiesen; lösche L und ersetze das alte Vorkommen von E durch den linken/rechten Nachfolger F von E 6 Aufgabe 4.1: Beispiel: Löschen des Knotens mit Nr. 84 (dient nur zur Veranschaulichung, gehört nicht zur Musterlösung) 65 zL 44 16 8 4 34 84 57 55 50 56 L 70 60 90 73 67 72 88 99 80 E 96 75 F 74 77 7 Aufgabe 4.1: Beispiel: Löschen des Knotens mit Nr. 84 (dient nur zur Veranschaulichung, gehört nicht zur Musterlösung) 65 zL 44 16 8 4 34 80 57 55 50 56 E 70 60 90 73 67 72 74 88 75 F 99 96 77 8 Aufgabe 4.1 (Fortsetzung): Der schwierige Fall im Algorithmus ist der, dass der zu löschende Knoten L Nachfolger hat, d.h. kein Blatt ist. Aufgabe 4.2: Korrektheit des Algorithmus Gezeigt werden muss, dass das Ergebnis wieder ein binärer Suchbaum ist. Dazu muss gezeigt werden, dass die Ersetzung von L durch E und von E durch F die Suchbaumeingenschaft nicht zerstört • Ersetzung von L durch E: E muss der größte (kleinste) Knoten im linken (rechten) Teilbaum unter L sein: Man betrachtet den Pfad, der von E zu L führt. Alle Knoten auf diesem Pfad sind kleiner als E (da E links von diesen Knoten liegt), und alle linken Teilbäume dieser Knoten sind ebenfalls kleiner als E. Folglich ist E der größte Knoten im linken Teilbaum unter L. Der Beweis, dass E der kleinste Knoten im rechten Teilbaum unter L ist, verläuft analog. • Ersetzung von E durch F: Wenn E rechter (linker) Nachfolger eines Knotens K ist, d.h. E > K (E < K), dann ist auch jeder Knoten des Teilbaumes unter E größer (kleiner) als K. 9 Aufgabe 5: Anwendung des Algorithmus von Dijkstra A 30 90 100 B 40 E 40 10 20 C 10 D 10 Schritt 1 A 30 90 100 90 B 40 30 E 40 20 10 C 10 D 100 Grüner Knoten mit minimalen Kosten ist E (30) 11 Schritt 2 A 30 90 100 90 B 40 30 E 40 20 10 C 10 D 30 + 40 = 70 < 100 AEC statt AC 70 40 Grüner Knoten mit minimalen Kosten ist D (40) 12 Schritt 3 A 30 90 100 50 B 40 30 E 40 20 10 C 10 D 40 30 + 10 + 10 = 50 < 90 AEDB statt AB 70 Grüner Knoten mit minimalen Kosten ist B (50) 13 Schritt 4 A 30 90 100 50 B 40 30 E 40 10 20 C 10 D 70 30 + 40 = 70 < 30 + 10 + 10 = 70 40 AEC bleibt (statt AEDB) Grüner Knoten mit minimalen Kosten ist C (70) 14 Schritt 5 A 30 90 100 50 B 40 30 E 40 10 20 C 10 D 70 40 15 Alternative Lösung A 30 90 100 50 B 40 30 E 40 10 20 C 10 D 70 40 16 So könnte die Lösung auch aussehen: Aktuell bearb. Knoten Liste der grünen Knoten (A,0) (A,0) (E,30), (C,100), (B,90) (E,30) (D,40), (C,70), (B,90) (D,40) (C,70), (B,50) (B,50) (C,70) (C,70) 17 Aufgabe 6: Dijkstra: Unterscheidung Entfernung und Fahrzeit • Beides sind Kosten • Zwei Attribute an Kanten; Dijkstra muss wissen, welches verwendet werden soll 18 Aufgabe 7.1: Heap 7 22 9 23 11 27 30 90 28 7 22 9 23 27 11 30 90 28 33 80 19 7019 33 80 19 70 Aufgabe 7.2: 7 9 22 27 23 90 7 22 28 33 11 80 19 30 70 8 9 23 27 11 30 90 28 33 80 19 7020 8 Aufgabe 7.2: 7 22 9 27 23 90 7 22 28 33 9 23 27 11 11 80 19 8 70 30 8 90 28 33 80 19 7021 30 Aufgabe 7.2: 7 8 22 27 23 90 7 22 28 33 8 23 27 11 11 80 19 9 70 30 9 90 28 33 80 19 7022 30 Aufgabe 7.3: Wie gut ist Array-Repräsentation auch für AVL-Bäume geeignet? Nicht gut. • Lücken in Array (AVL-Baum nicht ausgeglichen) • Einfügen/Löschen von Knoten erfordert Rotationen: Fast jedes Arrayelement muss verschoben werden: O(n) => Beispiel L-Rotation (die folgende Animation ist zur Lösung der Aufgabe nicht erforderlich; sie dient nur der Veranschaulichung) 23 L-Rotation +1 k1 0 k2 T1 T2 T3 24 L-Rotation: Einfügen von x +2 k1 +1 k2 T1 T2 T3 x 25 L-Rotation +2 k1 +1 k2 T1 T2 T3 x 26 L-Rotation k1 +2 +1 k2 T1 T2 T3 x 27 L-Rotation 0 k2 k1 0 T3 T1 T2 x 28 L-Rotation Position jedes Arrayelements verändert 0 k2 k1 0 T3 T1 T2 x 29 Aufgabe 8: Zusammenhang eines Graphen 4 Alternative Lösungen: • Tiefensuche: Graph zusammenhängend, wenn alle Knoten durchlaufen werden • Breitensuche (wie Tiefensuche) • Dijkstra (beliebige Kosten > 0, wie Tiefensuche) • Floyd (beliebige Kosten > 0), Graph zusammenhängend, wenn kein in Matrix Ein Problem ergibt sich daraus, dass die Definition "zusammenhängend" in der Aufgabenstellung von ungerichteten Graphen ausgeht, die vier Verfahren jedoch von gerichteten. Der unten gezeigte gerichtete Graph ist zusammenhängend; mit Startknoten B würde jedoch Dijkstra niemals A erreichen. Eine Lösung dieses Problems besteht darin, bei gerichteten Graphen zu jeder Kante eine Kante mit entgegengesetzter Orientierung zu ergänzen. Ungerichtete Kanten müssen durch zwei gerichtete Kanten mit entgegengesetzter Orientierung ersetzt werden. A 30 B Aufgabe 9: Anwendung von Scan-Line A D B Schnittpunkt erkannt (bereits vorhanden) C D DA A CD D C B C A D C A C D A C C A A 31