ETH Zürich Institut für Theoretische Informatik Prof. Dr. Angelika Steger Ralph Keusch, Florian Meier HS 2016 Algorithmen und Komplexität Lösungsvorschlag zu Übungsblatt 9 Lösungsvorschlag zu Aufgabe 1 Unsere Implementation verwendet zwei Stacks S1 und S2 . Für die Queue implementieren wir die Operationen E NQUEUE ( x ) und D EQUEUE () wie folgt: Algorithm 1 E NQUEUE ( x ) S1 .P USH ( x ) Algorithm 2 D EQUEUE () if S2 .isEmpty then while not S1 .isEmpty do S2 .P USH (S1 .P OP ) end while end if return S2 .P OP () Korrektheit: Wir zeigen, dass D EQUEUE () immer jenes Objekt y zurückgibt, welches unter allen Objekten in der Queue als erstes mit E NQUEUE (y) eingefügt wurde. Das ist so, da das oberste Objekt in S2 immer dieses y ist und der Algorithmus immer das oberste Objekt aus S2 zurückgibt. Amortisierte Laufzeit: Wir definieren die Potentialfunktion φ(i ) und die Funktion t(i ) wie folgt: φ(i ) := 2 · (Anzahl Objekte in S1 nach den ersten i Operationen) t(i ) := Zeit, die während den ersten i Operationen aufgewendet wurde. Wir nehmen an, dass die Operationen P OP und P USH jeweils nur einen Zeitschritt brauchen. Offensichtlich gilt φ(0) = t(0) = 0 und φ(i ) ist immer positiv. Wir zeigen per Induktion über i, dass t(i ) + φ(i ) ≤ 3i. (Dies ergibt die obere Schranke t(i ) ≤ t(i ) + φ(i ) ≤ 3i, da φ(i ) ≥ 0.) Die Induktionsverankerung i = 0 ist trivial. Die Induktionshypothese ist t(i − 1) + φ(i − 1) ≤ 3(i − 1) = 3i − 3. Falls nun die die i-te Operation E NQUEUE ist, finden wir t ( i ) + φ ( i ) = t ( i − 1) + 1 + φ ( i − 1) + 2 = t(i − 1) + φ(i − 1) + 3 ≤ 3i − 3 + 3 = 3i. Falls die i-te Operation D EQUEUE ist, unterscheiden wir, ob S2 leer ist oder nicht. Wenn S2 nicht leer ist, ergibt sich t ( i ) + φ ( i ) = t ( i − 1) + 1 + φ ( i − 1) = t(i − 1) + φ(i − 1) + 1 ≤ 3i − 3 + 1 < 3i. Falls S2 leer ist, müssen für jedes Objekt in S1 genau 2 Operationen (ein P OP von S1 und ein P USH auf S2 ) ausgeführt werden. Am Ende wird ein zusätzliches P OP auf S2 aufgerufen. Gemäss Definition von φ ist die Anzahl benötigter Operationen genau φ(i − 1) + 1 und folglich gilt t ( i ) + φ ( i ) = t ( i − 1) + φ ( i − 1) + 1 + 0 ≤ 3i − 3 + 1 + 0 < 3i. 1 Lösungsvorschlag zu Aufgabe 2 Wir nehmen an, dass sich die k Operationen aufteilen in eine erste Phase von k1 MakeNewSet- oder Union-Operationen und eine zweite Phase von k2 = k − k1 Find-Operationen. Da die MakeNewSetund Union-Operationen jeweils in konstanter Zeit O(1) ausführbar sind, sind die Gesamtkosten der ersten Phase O(k1 ). Um die Laufzeit der zweiten Phase zu berechnen, definieren wir die Potentialfunktion φ( j) als die Anzahl der Knoten, die nach insgesamt j Operationen von ihrer Wurzel mindestens Abstand 2 haben. Während der ersten Phase können wir höchstens k1 viele Knoten in die Struktur einfügen, damit gilt sicher φ(k1 ) ≤ k1 . Wenn nun mit der i-ten Find-Operation ein Knoten der Tiefe mi aufgerufen wird, so verursacht das Zurücklaufen zur Wurzel (und die Path-Compression) Kosten Cmi für eine geeignet gewählte Konstante C. Da wir aber gleichzeitig Path-Compression durchführen, d.h. die mi − 1 Knoten, die wir auf dem Weg zur Wurzel besuchen, direkt an die Wurzel hängen, verringert sich gleichzeitig das Potential um mi − 1. Wir haben während der zweiten Phase keine anderen Operationen, somit verringert sich das Potential während der zweiten Phase insgesamt um ∑ik=2 1 (mi − 1). Nun stellen wir aber fest, das per Definition φ( j) ≥ 0 für alle j gilt. Deshalb gilt insbesondere k2 k2 i =1 i =1 0 ≤ φ ( k ) = φ ( k 1 ) − ∑ ( m i − 1) ≤ k 1 − ∑ ( m i − 1). Darus folgt dann k2 ∑ ( m i − 1) ≤ k 1 . i =1 Die Gesamtkosten betragen deshalb höchstens k2 O(k1 ) + ∑ Cmi = O(k1 ) + C i =1 k2 ∑ ( m i − 1) + k 2 ! ≤ O(k1 ) + C (k1 + k2 ) = O(k) . i =1 Lösungsvorschlag zu Aufgabe 3 Für den Schlüssel k werden der Reihe nach die Speicherplätze h(k, 0), h(k, 1), h(k, 2), . . . betrachtet, und k wird im ersten freien Speicherplatz abgespeichert. Die Position, an der im i + 1-ten Versuch nachgeschaut wird, ist für die verwendete Hash-Funktion p(i ) = h(k, i ) = (h1 (k ) + ih2 (k )) mod m. Wir stellen zunächst fest, dass p(m) = p(0), denn p(m) = h1 (k) + mh2 (k ) mod m = h1 (k). Abhängig von h2 (k) und m werden wir aber bereits früher wieder am Platz p(0) landen. Wir überlegen uns, für welches j > 0 das zum ersten Mal passiert. Da d der grösste gemeinsame Teiler von h2 (k ) und m ist, existieren teilerfremde natürliche Zahlen α, β mit h2 (k) = αd und m = βd. Wegen p( β) = (h1 (k) + βh2 (k )) mod m = (h1 (k) + βαd) mod m = (h1 (k) + αm) mod m = (h1 (k)) mod m = p (0) 2 sind wir bereits nach β = m/d Schritten wieder am Ausgangsort und schauen danach nur noch Speicherplätze an, die wir vorher bereits betrachtet haben. Da α und β teilerfremd sind, folgt ähnlich für 0 < j < β, dass p( j) = (h1 (k ) + jαd) mod m = (h1 (k) + ( jα/β) m) mod m | {z } 6 ∈N 6 = p (0). Wir kommen also genau nach β = m/d Schritten wieder am Ausgangspunkt an, und dabei wird genau ein d-tel der m Speicherplätze betrachtet. 3