= Skizze der Lösungen zur Klausur zur Vorlesung "Informatik II: Algorithmen und Datenstrukturen" im SS 2017 = Alle Angaben ohne Gewähr + hier wird sich in den nächsten Stunden noch einiges ändern. == AUFGABE 1 (O-Notation und Laufzeitbestimmung), Korrektur: Claudius Korzen == === 1.1 (5 Punkte) sqrt(n) = O(n) aber nicht Theta(n) === sqrt(n) <= 1 * n für alle n >= 1. Daraus folgt sqrt(n) = O(n) mit C = 1 und n,,0,, = 1. (2 Punkte) Angenommen sqrt(n) = Omega(n). Dann würde es C und n,,0,, geben, so dass für alle n >= n0 gilt: sqrt(n) >= C * n. Letzter Ausdruck impliziert n >= C² * n² (auf beiden Seiten quadrieren), darauf folgt 1 >= C² * n und somit n <= 1 / C². Das kann aber nicht sein für alle n >= n0. Also war die Annahme, dass sqrt(n) = Omega(n) falsch. Und damit ist sqrt(n) nicht Theta(n). === 1.2 (5 Punkte) Umformung von 2 hoch log_4 n === 2 hoch (log,,4,,n) = (4 hoch (1/2)) hoch (log,,4,, n) = (4 hoch log,,4,, n) hoch (1/2) = n hoch 1/2 = sqrt(n) === 1.3 (10 Punkte) Laufzeit für T(n) <= 2 * T(n/4) + A * sqrt(n) === Durch wiederholtes Einsetzen der Formel auf der rechten Seite (analog dazu, wie wir es in VL2a und für das ÜB2 gemacht haben), erhält man: T(n)<
> <= 2 * T(n/4) + A * sqrt(n)<
> <= 2 * (2 * T(n/4^2^) + A * sqrt(n/4)) + A * sqrt(n) = 2^2^ * T(n/4^2^) + 2 * sqrt(n)<
> <= 2^2^ * (2 * T(n/4^3^) + A * sqrt(n/4^2^)) + 2 * sqrt(n) = 2^3^ * T(n/4^3^) + 3 * A * sqrt(n)<
> <= ...<
> <= 2^k^ * T(n/4^k^) + k * A * sqrt(n) Für k = log,,4,, n enthält man T(n) <= sqrt(n) + log,,4,, n * A * sqrt(n) = Theta(sqrt(n) * log n) == AUFGABE 2 (Programm, Laufzeitbestimmung, Hashing), Korrektur: Markus Näther == === 2.1 (5 Punkte) Funktion num_unique, die Anzahl der verschiedenen Zahlen ausgibt === {{{ def num_uniq(elements): uniqs = {} for x in input: if x not in uniqs: uniqs[x] = 1 return len(unqis.keys()) }}} Wenn man len(...) nicht kannte, konnte man auch einfach explizit mitzählen. === 2.2 (5 Punkte) Laufzeit der Funktion num_unique aus Aufgabe 2.1 === Es gibt n Schleifendurchläufe. Annahme: das "not in" und das "uniqs[x] = 1" gehen amortisiert in Zeit Theta(1). Dann ist die Laufzeit insgesamt Theta(n). === 2.3 (5 Punkte) Erwartungswert der Ausgabe, falls Elemente zufällig aus {0, 1} === Pr(alle Elemente 0) = 2^^^-n^<
> Pr(alle Elemente 1) = 2^^^-n^<
> PR(der Rest) = 1 - 2 * 2^^^-n^<
> Nach der Definition des Erwartungswertes ist dann<
> E(Anzahl verschiede Elemente) = 1 * 2^^^-n^ + 1 * 2^^^-n^ + 2 * (1 - 2 * 2^^^-n^) = 2 * (1 - 2^^^-n^). Für n = 3 ist das 1.75 == 2.4 (5 Punkte) 1-Universalität der gegebenen Klasse == Die Klasse hat nur 1 Element. Laut Definition ist sie dann 1-universell, wenn für jedes Schlüsselpaar x, y in U mit x != < gilt, dass |{h in H: h(x) == h(y)}| <= 1/m. Da unter h kein solches Schlüsselpaar kollidiert, ist die linke Seite = 0. Damit ist diese Klasse 1-universell. <
> <
> == AUFGABE 3 (Potenzialfunktion), Korrektur: Björn Buchhold == === 3.1 (5 Punkte) Funktion binary_inc, die einen Binärzähler der Länge n, gegeben als Feld, hochzählt === {{{ def binary_inc(counter): for i in range(len(counter) - 1, -1, -1): if counter[i] == 1: counter[i] = 0 else: counter[i] = 1 return return }}} === 3.2 (5 Punkte) Laufzeit von binary_inc als Theta(...) in Abhängigkeit von geeigneter Größe === Sei m die Anzahl Nullen "von rechts" (also bis zur ersten 1 bzw. n wenn alle 0) nach der Operation. Dann ist die Laufzeit gerade Theta(m). Begründung: die Schleife läuft von rechts nach links durch und in jeden Schleifendurchlauf wird das entsprechende Element auf 0 gesetzt und dann am Ende (falls nicht alle auf 0 gesetzt) eine 1 === 3.3 (5 Punkte) Gesamtlaufzeit von 2^n aufeinanderfolgenden Aufrufen === Sei Phi_i die Anzahl Nullen "von rechts" (wie oben) nach der i-iten Operation.<
> Fall 1: Phi,,i,, = 0. Dann hat die Operation einfach nur die letzte Stelle erhöht und die Kosten waren Theta(1) und das Potential hat sich um 1 geändert.<
> Fall 2: Phi,,i,, > 0. Dann ist T,,i,, = Theta(Phi,,i,,) gemäß Aufgabe 3.2 und vor der Operation stand an der letzten Stelle eine 1, also Phi,,i-1,, = 0. In beiden Fällen ist T,,i,, <= A + B * (Phi,,i,, - Phi,,i-1,,), für geeignete A und B. Damit ist die Gesamtlaufzeit Theta(2^n^). === 3.4 (5 Punkte) Gesamtlaufzeit, wenn bei einem anderen Zählerstand begonnen wird === Die Kosten sind identisch. Begründung: die Menge der Argumente, mit denen die Funktion aufgerufen wird, ist identisch. Nur die Reihenfolge ist eine andere. <
> <
> == AUFGABE 4 (Bäume), Korrektur: Axel Lehmann == === 4.1 (5 Punkte) Vollständiger binärer Baum der Tiefe 2 (3 Ebenen), der die Heapeigenschaft erfüllt, aber kein binärer Suchbaum ist === {{{ 1 / \ 2 3 / \ / \ 4 5 6 7 }}} Begründung: Heapeigenschaft erfüllt (jeder Knoten ist größer als sein Elternknoten), aber kein binärer Suchbaum (zum Beispiel müsste für die Wurzel gelte: größer als linkes Kind, kleiner als rechtes Kind; das ist nicht der Fall). === 4.2 (5 Punkte) Vollständiger ternärer Baum der Tiefe 2 (3 Ebenen), mit Knoten von 1 bis 13 durchnummeriert === {{{ 1 / | \ 2 3 4 / | \ / | \ / | \ 5 6 7 8 9 10 11 12 13 }}} === 4.3 (5 Punkte) Anzahl Knoten in einem vollständigen ternären Baum in Abhängigkeit der Tiefe d === Nehmen wir an, die Wurzel ist auf Tiefe 0. Dann:<
Anzahl Knoten mit Tiefe 0: 1 = 3^^^0^<
> Anzahl Knoten mit Tiefe 1: 3 = 3^^^1^<
> Anzahl Knoten mit Tiefe 2: 9 = 3^^^2^<
> Usw. Anzahl Knoten bei Gesamttiefe d: 3^^^0^ + 3^^^1^ + ... + 3^^^d^<
> Laut gegebener Formel ist das: (3^^^d+1^ - 1) / (3 - 1) = 1/2 * (3^^^d+1^ - 1) === 4.4 (5 Punkte) Indizes der Kinder und des Elternknoten für Knoten mit Index i === Indizes Kinder: 3i - 1, 3i, 3i + 1<
> Index Elternknoten: (i + 1) / 3 abgerundet <
> == AUFGABE 5 (Graphen), Korrektur: Niklas Schnelle == === 5.1 (5 Punkte) Adjazenzlisten und Länge der kürzesten Wege === Die Adjazentlisten sehen so aus (wenn jede ungerichtete Kante, als Kante in beide Richtungen eingetragen wird): u,,1,,: u,,2,,, u,,3,, ... u,,2,,: u,,1,,, u,,3,,, u,,4,, ... u,,3,,: u,,1,,, u,,2,,, u,,5,, ... u,,4,,: u,,2,, ... u,,5,,: u,,3,, Die Längen der kürzesten Wege: Für {u,,1,,, u,,2,,}, {u,,1,,, u,,3,,}, {u,,2,,, u,,3,,}, {u,,2,,, u,,4,,}, {u,,3,,, u,,5,,} : jeweils 1<
> Für {u,,1,,, u,,4,,}, {u,,1,,, u,,5,,}, {u,,2,,, u,,5,,}, {u,,3,,, u,,4,,} : jeweils 2<
> Für {u,,4,,, u,,5,,}: 3 === 5.2 (5 Punkte) Funktion für Länge des kürzesten Weges zwischen u und v === {{{ def dist(adjacency_lists, u, v): visited = {} nodes = [(u, 0)] while len(nodes) > 0: (x, dist) = nodes.pop(0) if x in visited: continue visited[x] = 1 if x == v: return dist for y in adjacency_lists[x]: nodes.append((y, dist + 1)) }}} Bemerkung: da der Graph zusammenhängend ist, wird v sicher erreicht und man braucht kein zusätzliches return am Ende. === 5.3 (5 Punkte) Laufzeit der Funktionen dist === Wie bei BFS wird jeder Knoten und jede Kante genau einmal angeschaut.<
> Die Laufzeit ist deshalb Theta(n + m).<
> Da der Graph zusammenhängend ist, und somit m >= n - 1, ist das Theta(m).<
> == AUFGABE 6 (String-Suche), Korrektur: Hannah Bast == === 6.1 (10 Punkte) Funktion für vereinfachten Karp-Rabin mit Musterlänge 3 === {{{ def num_hits(T, P): if len(T) < 3: return 0 w = 100 * T[0] + 10 * T[1] + T[2] p = 100 * P[0] + 10 * P[1] + P[2] hit_count = 0 for i in range(3, len(T)): if w == p: hit_count += 1 w = 10 * (w % 100) + T[i] return hit_count }}} === 6.2 (5 Punkte) Anzahl Blockoperationen der Funktion num_hits === Die Funktion geht einmal von vorne nach hinten durch das Feld der Länge n. Die Anzahl Blockoperationen ist von daher Theta(n / B). === 6.3 (5 Punkte) Erwartete Anzahl Treffer bei zufälligem Text und Muster === Sei I,,i,, = 1 genau dann wenn das Muster an Stelle matched. Die Wahrscheinlichkeit, dass eine feste Stelle des Musters zu einer festen Stelle des Textes passt ist 10 * 1/10 * 1/10 = 1/10. Also ist Pr(I,,i,, = 1) = 1/1000, für i = 0, ..., n-3 (an der vorletzten und letzten Stelle kann ein Muster der Länge 3 nicht mehr matchen). Die erwartete Anzahl Matches ist gerade sum,,i=0,...,n-3,, E I,,i,, = sum,,i=0,...,n-3,, Pr(I,,i,, = 1) = (n - 2)/1000. Für n = 3 ist das 1/1000. Für n = 1002 ist das 1.