Prof. Dr. V. Linnemann Universität zu Lübeck Institut für Informationssysteme Lübeck, den 5. Oktober 2010 Algorithmen und Datenstrukturen Sommersemester 2010 2. Klausur Lösungen Hinweis: • Achten Sie bei Programmieraufgaben darauf, Ihre Lösungen zu kommentieren, so dass es möglich ist, den Lösungsweg zu verfolgen und die Idee der gewählten Implementierung zu verstehen. Fehlende Kommentare führen zu einem erheblichen Punktabzug • Insgesamt sind bei dieser Klausur 80 Punkte zu erzielen. Zum Bestehen der Klausur genügen 50% der Punkte Lösung 1: Laufzeitstack Was bewirkt die wasmachich - Methode der folgenden Klasse ? Was bewirkt die main Methode ? Zeichnen Sie den Laufzeitstack jeweils zu Ende eines wasmachich - Aufrufs, nachdem der Ergebniswert im Stack vermerkt wurde. Verwenden Sie als Rückkehradresse jeweils den in der Klasse angegebenen Kommentar. public class WasMachIch { public static int wasmachich(String wort, char zeichen, int i){ if (i>=wort.length()) return 0; if (wort.charAt(i)==zeichen) return wasmachich(wort, zeichen, i+1) /* A */ +1; else return wasmachich(wort, zeichen, i+1) /* B */; } public static void main (String[] args){ int ergebnis = wasmachich("BONN",’N’,0) /* C */; System.out.println(ergebnis); } } (8 Punkte) 1/12 Lösung: wasmachich liefert als int - Wert, wie oft das Zeichen zeichen in der Zeichenkette wort ab der Position mit dem Index i vorkommt, d.h. bei i = 0 (1. Aufruf) wird abgeliefert, wie oft das Zeichen zeichen in der Zeichenkette wort vorkommt. Die main - Methode gibt über die Konsole aus, wie häufig der Buchstabe N in der Zeichenkette BON N vorkommt, d.h. es wird die Zahl 2 ausgegeben Laufzeitstack: Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse BONN N 0 C BONN N 1 B BONN N 2 B BONN N 3 A 0 BONN N 4 A Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse BONN N 0 C BONN N 1 B BONN N 2 B 1 BONN N 3 A Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse BONN N 0 C BONN N 1 B 2 BONN N 2 B Ergebnis wort zeichen i Rückkehradresse Ergebnis wort zeichen i Rückkehradresse BONN N 0 C 2 BONN N 1 B Ergebnis wort zeichen i Rückkehradresse 2 BONN N 0 C 2/12 Lösung 2: Backtracking mit Rekursion Auf einem n∗n Schachbrett soll eine Konfiguration gefunden werden, wo genau eine Dame in jeder Zeile steht und keine Dame eine andere bedroht. Eine Dame auf einem Feld wird genau dann nicht bedroht, wenn sich keine Dame in der gleichen Zeile, in der gleichen Spalte oder diagonal befindet. Beispiele für n=4, eine Dame ist jeweils durch „1“ markiert: 0 0 1 2 3 2 1 3 Gegenseitige Bedrohungen: keine 1 1 1 0 0 1 2 3 1 1 1 2 1 3 1 1 Die beiden Damen in Zeile 0 bedrohen sich gegenseitig Die beiden Damen in Spalte 1 bedrohen sich gegenseitig Die Damen in den Positionen (0,1) und (1,0) bedrohen sich gegenseitig Die Damen in den Positionen (0,1) und (2,3) bedrohen sich gegenseitig 1 In dem folgenden Java-Programm fehlt noch die Methode plaziere, die Sie im Rahmen dieser Aufgabe realisieren sollen. Diese Methode soll ab der durch den Parameter gegebenen Zeile eine Dame pro Zeile bedrohungsfrei plazieren, unter der Voraussetzung, dass in den vorhergehenden Zeilen bereits je eine Dame bedrohungsfrei plaziert wurde. Die Methode plaziere muss jeweils alle Felder der Zeile probieren und dann die Methode plaziere für die nächste Zeile rekursiv aufrufen. Wichtig: Es genügt, eine Damen-Belegung zu finden import simpleio.*; public class Schachbrett { static int[][] feld; // Feld für Schachbrett // 0: auf dem Feldelement befindet sich keine Dame // 1: auf dem Feldelement befindet sich eine Dame static int feldgroesse; /* * bedroht * Element * * @return * * */ // Anzahl der Elemente pro Zeile und Spalte ueberprueft, ob bei der aktuellen Schachbrettbelegung das feld[zeile][spalte] bedrohungsfrei gesetzt werden kann true: feld[zeile][spalte] ist bedroht, kann also nicht gesetzt werden false: feld[zeile][spalte] ist nicht bedroht public static boolean bedroht(int zeile, int spalte){ // 3/12 // // // // // Die Methode "bedroht" koennen Sie als gegeben voraussetzen. Die Implementierung dieser Methode ist für die Aufgabe nicht relevant und wird daher der Übersicht halber hier nicht angegeben } /** * plaziere geht davon aus, dass in den Zeilen * bis einschliesslich "zeile"-1 bereits * je eine Dame bedrohungsfrei pro Zeile gesetzt ist. * plaziere versucht, das Feld ab der Zeile "zeile" * bedrohungsfrei mit je einer Dame * pro Zeile zu besetzen. Wenn dieses gelingt, * wird true abgeliefert, andernfalls false. * * Der erste Aufruf von plaziere muss sein: * * plaziere(0) . * Zeilenindex der Zeile, die als * @param zeile: naechstes gesetzt werden muss * * * @return true: zeile >= feldgroesse, oder Besetzung ohne Bedrohung ist gefunden * und in feld markiert * false: Besetzung nicht moeglich * */ public static boolean plaziere(int zeile) { // // Diese Methode ist im Rahmen der Aufgabe zu formulieren // } public static void ausgabe (Ausgabe a){ a.println(); for (int i=0; i<feld.length; i++) { for (int j=0; j<feld[i].length; j++) a.print(feld[i][j] + " "); a.println(); } } public static void main(String[] args) { Ausgabe a = Ausgabe.oeffnen(); 4/12 if (args.length==0) feldgroesse = 8; else feldgroesse = Integer.parseInt(args[0]); feld = new int[feldgroesse][feldgroesse]; a.println(); if (plaziere(0)){ a.println("Eine Loesung ist:"); ausgabe(a); } else { a.println("Es gibt keine Loesung !!"); ausgabe(a); } } } Achten Sie darauf, dass Sie Ihre Lösung mit genügend Kommentar versehen, damit man die Lösung nachvollziehen kann. Hinweis zum Aufwand: Es gibt eine Lösung, die einschließlich Kommentar nur aus 28 Zeilen besteht. (20 Punkte) Lösung: public static boolean plaziere(int zeile) { // // Ueberpruefung, ob das Ende des Schachbretts erreicht ist // if (zeile>=feldgroesse) return true; // // alle Elemente der Zeile, die nicht bedroht sind, probieren // for (int spalte=0; spalte<feldgroesse; spalte++){ if(!bedroht(zeile,spalte)) { // // Element setzen und naechste Zeile plazieren // feld[zeile][spalte] = 1; if (plaziere(zeile+1)) return true; // // kein Erfolg, d.h. Element zuruecksetzen // und naechste Spalte probieren // feld[zeile][spalte] = 0; 5/12 } } // // alle Spalten haben zu keinem Erfolg gefuehrt, d.h. es gibt // keine bedrohungsfreie Belegung der aktuellen Zeile // return false; } Lösung 3: Hashtabelle Gegeben sei die unten abgebildete Hashtabelle, bei der die Schlüssel mit der Hashfunktion h(k) = Querprodukt(k) M OD 11 bestimmt werden. Für eine Zahl z = z1 z2 ...zn wird hierbei definiert: Querprodukt(z1 z2 ...zn ) = z1 ∗ z2 ∗ ... ∗ zn Als Kollisionsstrategie wird lineares Sondieren angewandt, d. h. hi (k) = (hi−1 (k)+1) M OD 11. a) Fügen Sie die Werte 14, 11, 20 in der angegebenen Reihenfolge in die folgende Hashtabelle ein und geben Sie dabei die Anzahl der aufgetretenen Kollisionen an. 0 1 2 30 26 38 3 4 5 6 7 8 9 18 33 10 (6 Punkte) b) Welches Problem tritt beim Löschen auf, wenn beim vorausgegangenen Einfügen Kollisionen aufgetreten sind? Wie kann man das Problem lösen ? (3 Punkte) Lösung: a) Einfügen der Werte 14, 11, 20 in der angegebenen Reihenfolge ergibt: • Einfügen der 14 mit h(14) = 4 M OD 11 = 4: 0 1 2 30 26 38 3 4 5 6 7 14 8 9 18 33 10 • Einfügen der 11 mit h(11) = 1 M OD 11 = 1 (Kollision → h1 (11) = (1 + 1) M OD 11 = 2, Kollision → h2 (11) = (2 + 1) M OD 11 = 3): 0 1 2 3 4 30 26 38 11 14 5 6 7 8 9 18 33 10 • Einfügen der 20 mit h(20) = 0 M OD 11 = 0 (Kollision → h1 (20) = (0 + 1) M OD 11 = 1, Kollision → h2 (20) = (1 + 1) M OD 11 = 2, Kollision → h3 (20) = (2 + 1) M OD 11 = 3, Kollision → h4 (20) = (3 + 1) M OD 11 = 4, Kollision → h5 (20) = (4 + 1) M OD 11 = 5): 0 1 2 3 4 5 30 26 38 11 14 20 6 7 8 9 18 33 6/12 10 b) Einträge müssen als gelöscht markiert werden, da sonst mit Kollisionen eingetragene Werte nicht mehr wiedergefunden werden, die Sondierkette ist unterbrochen. Alternativ muss die Tabelle so umorganisiert werden, dass die Sondierketten nicht unterbrochen werden Lösung 4: Sortierverfahren a) Es seien eine sortierte Liste L und eine unsortierte Liste L0 gegeben, wobei L sehr lang im Vergleich zu L0 ist. Eine Möglichkeit, die aus den Elementen von L und L0 bestehende Liste zu sortieren, besteht darin, L0 ans Ende von L anzufügen und die so entstandene Liste zu sortieren. Wählen Sie aus den Verfahren Sortieren durch direktes Aussuchen, Sortieren durch direktes Einfügen und Heapsort dasjenige aus, welches diese Aufgabe am effizientesten löst und begründen Sie Ihre Antwort (8 Punkte) b) Welche Laufzeit hat Quicksort im Mittel und im schlechtesten Fall ? (6 Punkte) c) Wieviele Schlüsselvergleiche benötigt jedes allgemeine Sortierverfahren zum Sortieren von N verschiedenen Schlüsseln sowohl im Mittel als auch im schlechtesten Fall mindestens ? (3 Punkte) Lösung: a) Von den Verfahren Sortieren durch direktes Aussuchen, Sortieren durch direktes Einfügen und Heapsort ist Sortieren durch direktes Einfügen das Verfahren, das in diesem Fall die gute Vorsortierung am besten ausnützt. Die Elemente aus der Liste L werden nur einmal durchlaufen, ohne dass Vertauschungen notwendig sind. Die wenigen Elemente von L0 müssen anschließend in den L-Teil durch Einfügen einsortiert werden. Die anderen Verfahren nutzen die Vorsortierung überhaupt nicht aus. b) im Mittel: O(N ∗ log(N )), im schlechtesten Fall: O(N 2 ) c) Ω(N ∗ log(N )) Lösung 5: Minimale Spannende Bäume Gegeben sei der folgende gewichtete, ungerichtete Graph: 7/12 H A 8 9 11 D 5 2 E B 1 6 3 F Führen Sie den Algorithmus von Kruskal zur Berechnung eines minimalen Spannenden Baumes durch. Geben Sie in jedem Schritt die betrachtete Kante und die einzelnen Teilbäume an. (12 Punkte) Lösung: H A 8 9 11 D 5 2 E B 1 6 3 F 1 D F wählen: 8/12 H A 8 9 11 D 5 2 E B 1 6 3 F 2 E B wählen: H A 8 9 11 D 5 2 E B 1 6 3 F 3 B F wählen: 9/12 H A 8 9 11 D 5 2 E B 1 6 3 F 5 D E verwerfen 6 E F verwerfen 8 D H wählen: H A 8 9 11 D 5 2 E B 1 6 3 F 9 H E verwerfen 11 B A wählen: 10/12 H A 8 9 11 D 5 2 E B 1 6 3 F Lösung 6: Kürzeste Wege Gegeben sei folgender gewichteter Graph G: a) Wenden Sie den Algorithmus von Dijkstra für kürzeste Wege auf den Graphen an. Der Startknoten sei die 1. Geben Sie die Zwischenschritte wie in der Vorlesung in einer Tabelle der folgenden Art an: Nr. . . . gewählt Entfernung Vorgänger . . . . . . Nr. . . . Randknoten Entfernung Vorgänger . . . . . . (12 Punkte) b) Fügen Sie eine ungerichtete Kante mit dem Gewicht −1 von Knoten 4 zu Knoten 3 in G ein. Entnehmen Sie jetzt G den kürzesten Weg von Knoten 1 zu Knoten 3, ohne den Algorithmus aus a) zu verwenden. (2 Punkte) Lösung: 11/12 a) Nr. 1 gewählt Entfernung Vorgänger 0 1 4 5 1 3 10 1 5 2 15 20 4 3 Nr. 2 3 4 2 3 5 2 5 2 Randknoten Entfernung Vorgänger 30 1 10 1 5 1 30 1 10 1 15 4 20 3 15 4 20 3 b) -1 Die ungerichtete Kante mit negativem Gewicht kann unendlich oft hin und her durchlaufen werden. Jedesmal reduziert sich die Gesamtlänge des Weges um eins. Die korrekte Antwort ist deshalb -∞. 12/12