Theoretische Informatik III Sommersemester 2003 Mitschriften von Michael Brückner Chemnitz, den 28.07.2003 1. Binomialer Heap 1.1 Der binomiale Baum Def.: Binomialer Baum B (BinTree) i sei ein einzelner Knoten und Bi+1 sei der Baum, welcher durch Verbinden zweier binomialer Bäume Bi entsteht (Verbinden der beiden Wurzeln durch eine Kante). B0 B1 B2 B3 … B0 Bem.: 1) 2) 3) 4) 5) Die Wurzel des i-ten binomialen Baumes Bi hat i Söhne. Diese Söhne sind Wurzeln der Bäume B0, B1, …, Bi-1. Die Tiefe des binomialen Baumes Bi ist i. Bi enthält 2i Knoten, d.h. |Bi| = 2i. Die Anzahl Knoten in Tiefe j ist i . j Beweis: 1-4) per Definition 5) Induktion über i: Induktionsanfang i = 0: 0 0 | B 0 | in Tiefe j = =1 Induktionsschritt i > 0, nach Induktionsvoraussetzung gelte für ∀j mit 0 ≤ j ≤ i – 1: i - 1 j | B i -1 | in Tiefe j = | B i | in Tiefe j = (| B i -1 | in Tiefe j) + (| B i -1 | in Tiefe j - 1) ⇔ Def.: Datenstruktur des binomialen Baumes Bsp.: B0 i j = i - 1 j i - 1 j - 1 + ■ Für die Struktur des binomialen Baumes gilt: 1) Jeder Knoten besitzt eine Verbindung zu seinem Vater. 2) Jeder Knoten besitzt eine Verbindung zu dem Sohn, welcher zuletzt hinzugefügt wurde (linkester bzw. rechtester Sohn). 3) Alle Söhne eines binomialen Baumes befinden sich in einer doppelt verketteten Ringliste. B1 B2 B3 … 2 Def.: Link(B, C) Verbindet zwei binomialer Bäume (mit Heap-Eigenschaft) B und C. 1) Vergleiche die Schlüsselwerte (key) der beiden Wurzeln wB und wC. o.B.d.A. sei key(wB) ≤ key(wC) 2) Gehe zu dem Sohn s von wB, welcher zuletzt hinzugefügt wurde. 3) Füge wC, als neuen Sohn von wB, in die doppelt verkettete Ringliste hinzu und verbinde wC mit wB. wB wC wB s wC s Laufzeit: O(1) 1.2 Der binomiale Heap Def.: Bsp.: Binomialer Heap h i (BinHeap) Ein binomialer Heap hi ist ein Feld der Größe i+1, wobei das k-te Feld (0 ≤ k ≤ i) auf NIL oder einen binomialen Baum Bk mit Heap-Eigenschaft zeigt. binomialer Heap h5 5 4 3 2 1 0 • • • • • • HeapSize = max { n | hi[n] ≠ NIL } |hi| = # Elemente im gesamten Heap | B HeapSize | = 2 HeapSize ≤ | h i | ≤ B0 B2 ∑2 HeapSize z = 2 HeapSize+1 − 1 z =0 B3 1.3 Operationen auf der Datenstruktur BinHeap 1.3.1 Verschmelzen zweier binomialer Heaps Def.: Melt(h, h’) Melt verschmelzt zwei binomiale Heaps h, h’ zu einem. Dies erfolgt ähnlich der binären Addition. Laufzeit (mit n = |h + h’|): O(ld n) Bsp.: h = 3 2 1 0 • • • • h’ = 3 2 1 0 • • • • h + h’ = 3 2 1 0 • • • • B0 B0 B1 B1 B1’’ B2 B3’’ 0111 Operation + Ergebnis 0011 = 1010 Übertrag 1) B0 + B0’ h[0] = NIL B1’’ = Link(B0, B0’) 2) B1 + B1’ + B1’’ h[1] = B1’’ B2’’ = Link(B1, B1’) 3) B2 + B2’’ h[2] = NIL B3’’ = Link(B2, B2’’) 4) B3’’ h[2] = B3’’ 0 3 1.3.2 Aufbau eines binomialen Heaps Def.: Make(x, D[x]) Erzeugt einen binomialen Heap h0 und fügt das Element x mit dem Schlüsselwert D[x] als binomialen Baum B0 in den Heap ein. Laufzeit: O(1) 1.3.3 Einfügen eines Elementes in einen binomialen Heap Def.: Insert(x, D[x], h) Fügt das Element x mit dem Schlüsselwert D[x] in den Heap h ein: Melt(h, Make(x, D[x])) Laufzeit (mit n = |h|): O(ld n) 1.3.4 Löschen des Minimums aus den binomialen Heap Def.: Delete(h) Löscht das Minimum aus dem binomialen Heap h: 1) Suche das Minimum an Stelle k mit zugehörigem binomialen Baum B (Wurzel von B sei w ). 2) Erzeuge mittels Make und Insert einen Heap h , welcher die Söhne von w enthält. 3) Lösche B aus h. 4) Verschmelze h und h . k k k-1 k k k k-1 Laufzeit (mit n = |h|): 1) Zeit für Durchlauf von 0 bis HeapSize: O(HeapSize) = O(ld n) 2) Zeit für Einfügen von k Elementen in h’: O(k) = O(HeapSize) = O(ld n) 3) Löschen von B : O(1) 4) Verschmelzen: O(ld n) k 1-4) O(ld n + ld n + 1 + ld n) = O(ld n) Satz: Das Einfügen von n Elementen in den Anfangs leeren binomialen Heap ist in O(n) möglich. Beweis: Gegeben sei: 1) leerer binomialer Heap h 2) Folge von Operationen σ , σ ,…, σ mit σ = Insert(i, i, h) 1 2 n i Betrachten die ersten Operationen: 1) Insert(1, 1, h) Make(1, 1) Melt(h, Make(1, 1)) → 0 x Link h = O(1) 0 x O(1) 2) Insert(2, 2, h) Make(2, 2) Melt(h, Make(2, 2)) → 1 x Link h = O(1) 1 x O(1) 3) Insert(3, 3, h) Make(3, 3) Melt(h, Make(3, 3)) → 0 x Link h = O(1) 0 x O(1) 4) Insert(4, 4, h) Make(4, 4) Melt(h, Make(4, 4)) → 2 x Link … h = O(1) 2 x O(1) 1 2 3 h = 0 1 h = h = h = 2 3 4 4 Amortisierende Analyse: Man zählt für jedes Insert sowohl die Zeit für ein Make O(1), als auch die Zeit für ein Link O(1), auch wenn kein Link durchgeführt wird (= „gespeicherte“ Zeit). Jedes Mal wenn sich die Anzahl Bäume im Heap um 1 erhöht, wird eine Zeiteinheit „gespeichert“; bleibt sie gleich wird „korrekt“ gezählt; verringert sie sich um n werden n Zeiteinheiten „aufgebraucht“. Damit nie mehr Zeiteinheiten benötigt werden, als zuvor gespeichert wurden, muss an jedem Baum im Heap eine Zeiteinheit „gespeichert“ werden. Das dies gilt, bleibt zu zeigen: t … Die für die Operation σ tatsächlich benötigte Zeit. a … Die für die Operation σ „gezählte“ Zeit (= amortisierte Zeit). φ … Die Anzahl Zeiteinheiten, welche in h „gespeichert“ sind (= Potential). Diese gleich der Anzahl Bäume im Heap, sodass φ ≥ 0. i i i i i i n Es soll gelten: Σ t i ≤Σa i = O(n) a =t +φ –φ Σ t = Σ (a – φ + φ ) = (Σ a ) – φ + φ = (Σ a ) – φ i i i i i i-1 i i-1 i n 0 i n ≤Σa i Da a = Zeit für 1 x Make + Zeit für 1 x Link = O(1) + O(1) gilt: Σ a = 2n = O(n) ≥ Σ t i Satz: i i ■ Die Zeit für n-maliges Einfügen in einen Anfangs leeren binomialen Heap ist O(n). Die mittlere Zeit für einmaliges Einfügen ist somit O(n)/n = O(1). Die worst-case Laufzeit ist O(ld n) (vgl. Insert). 1.4 Operationen auf der Datenstruktur BinHeap mit lazyMelt Def.: Bsp.: Binomialer Heap l mit lazyMelt i Ein binomialer Heap mit lazyMelt li ist eine doppelt verkettete Ringliste mit i Zeigern auf beliebige, binomiale Bäume (mit Heap-Eigenschaft). binomialer Heap mit lazyMelt l4 • • • • Bi1 Bi2 Bi3 Bi4 .1 Verschmelzen zweier binomialer Heaps mit lazyMelt 1.4 Def.: lazyMelt(l, l’) Verschmelzt zwei binomiale Heaps l und l’ durch Anhängen von l’ an l. Laufzeit: O(1) 1.4.2 Aufbau eines binomialen Heaps mit lazyMelt Def.: Make(x, D[x]) Erzeugt einen binomialen Heap l0 und fügt das Element x mit dem Schlüsselwert D[x] als binomialen Baum B0 in den Heap ein. Laufzeit: O(1) 5 1.4.3 Einfügen eines Elementes in einen binomialen Heap mit lazyMelt Def.: Insert(x, D[x], l) Fügt das Element x mit dem Schlüsselwert D[x] in den Heap l ein: lazyMelt(l, Make(x, D[x])) Laufzeit: O(1) 1.4.4 Löschen des Minimums aus den binomialen Heap mit lazyMelt Def.: Delete(l) Löscht das Minimum aus dem binomialen Heap l, welcher die Bäume Bi1 Bi2 Bim 1) Suche Minimum* mit zugehörigem binomialen Baum Bk (Wurzel von Bk sei wk). , 2) Erzeuge einen Heap hz mit z = ld ∑2 m x =1 ix , …, enthält. . 3) Füge alle Söhne von wk mit Melt in hz ein. 4) Lösche Bk aus l und füge alle in l verbliebenen binomiale Bäume mit Melt in hz ein. 5) Transformiere hz zu neuem l’. Laufzeit (mit n = |l|): 1) Wegen * gilt: O(ld n) 2) leeren Heap erstellen: O(1) 3) k Elemente (Söhne von Bk) mit Melt in hz einfügen: O(k) = O(ld n) 4) m – 1 Elemente (in l verbliebene Bäume) mit Melt in hz einfügen: O(m) 5) Transformation: O(ld n) Analog zum Beweis unter 1.3.4 wurde für jeden Baum eine Zeiteinheit „gespeichert“. 6) In lm befinden sich somit m gespeicherte Zeiteinheiten: O(m) Weiter muss, für zukünftige Operationen, für jeden Baum in hz eine Zeiteinheit gespeichert werden. 7) Dies sind maximal ld n viele: O(ld n) 1-7) O(ld n + 1 + ld n + m + ld n – m + ld n) = O(ld n) * Das Minimum kann als Pointer mitgeführt werden, sodass nur nach Delete ein neues Minimum gesucht werden muss. Dadurch müssen maximal O(ld n) Elemente durchmustert werden. 1.5 Laufzeit-Übersicht Laufzeit für die Operationen mit n = |h| bzw. n =|l|: Melt/lazyMelt Make Insert Delete BinHeap Worst-case Amortisiert O(ld n) O(ld n) O(1) O(1) O(ld n) O(1) O(ld n) O(ld n) BinHeap mit lazyMelt Worst-case Amortisiert O(1) O(1) O(1) O(1) O(1) O(1) O(n) O(ld n) 6 2. Fibonacci-Heap 2.1 Motivation Ziel: Die Laufzeit des Dijkstra-Algorithmus mittels Heap ist O(|V| * ld |V| + |E| * ld |V|). Für |E|≥|V| gilt: O(|E| * ld |V|) = O(|V|2 * ld |V|). Ziel ist es nun, eine Laufzeit von etwa O(|V| * ld |V|) zu erreichen. Idee: Innerhalb des Dijkstra-Algorithmus werden die Elemente im Heap sukzessive verringert (durch DecreaseKey). Das Wiederherstellen der Heap-Eigenschaft benötigt (bisher) O(ld n). Würde man den Knoten aus dem Heap „herausschneiden“, den Wert des Knotens verringern und diesen in den Heap wieder einfügen, erhielte man den selben Heap wie durch DecreaseKey. Dieses modifizierte DecreaseKey soll eine Laufzeit von O(1) haben. 2.2 Der Fibonacci-Heap Def.: Def.: 2.3 Fibonacci-Heap fi (FibHeap) Ein Fibonacci-Heap fi ist eine doppelt verkettete Ringliste (Wurzelliste) mit i Zeigern auf beliebige, nicht zwingend binomiale, Bäume (mit Heap-Eigenschaft). Von der Struktur ähnelt der Fibonacci-Heap einem binomialen Heap mit lazyMelt. Rang(v) Der Rang eines Knotens v ist gleich der Anzahl Söhne von v. Operationen auf der Datenstruktur FibHeap Bem.: Die Operationen lazyMelt, Make, Insert und Delete sind identisch zu denen des binomialen Heaps mit lazyMelt. Die im Fibonacci-Heap enthaltenen Bäume Tk mit Wurzel wk (k sei der Rang von wk) werden wie binomiale Bäume Bk behandelt. 2.3.1 Herausschneiden eines Knotens aus den Fibonacci-Heap Def.: Cut(x, f) Schneidet das Element x (mit Vater v) aus dem Fibonacci-Heap f heraus und fügt es als Wurzel eines neuen Baumes in f ein. 1) Lösche den Teilbaum Tk mit wk = x aus f und füge ihn mit lazyMelt in f ein. 2) Setze die neue Wurzel x auf „nicht markiert“. 3) Wenn v bereits „markiert“, Cut(v, f) 4) Ist v „nicht markiert“ und keine Wurzel in f, setze v auf „markiert“ Laufzeit: 1) lazyMelt: O(1) 2) Pointer setzen: O(1) 3) rekursiver Aufruf von Cut 4) Markieren: O(1) 1-4) O((m + 1) * (1 + 1 + 1)) = O(m) mit m = # rekursive Aufrufe von Cut 7 2.3.2 Def.: Schlüsselwert im Fibonacci-Heap verringern DecreaseKey(x, n, f) Ändert den Schlüsselwert von D[x] auf n. Dabei wird der Fibonacci-Heap f wie folgt umgebaut: 1) Suche Knoten x und vergleiche D[x] mit n. 2) Falls D[x] > n setze D[x] auf n und führe Cut(x, f) aus. x x Laufzeit: O(m) mit m = # rekursive Aufrufe von Cut Satz: Für alle im Fibonacci-Heap vorhandenen Bäume Tk mit Wurzel wk (k sei der Rang von wk) gilt: ∃c > 0 mit |Tk| ≥ 2c*k. Beweis: Wir betrachten den Anfangs leeren Fibonacci-Heap f. Durch Insert entsteht zunächst eine Kette von Bäumen, welche nur aus einem einzelnen Element bestehen. Für diese Bäume gilt obige Aussage. Größere Bäume können nur durch Melt (innerhalb von Delete) entstehen. Sei nun v ∈ f ein beliebiger Knoten (≠ Wurzel) mit Rang i und die Kinder von v nach der Zeit, zu der sie (durch Melt) zu v kamen geordnet. Sei wj das j-te Kind von v. v w1 w3 w3 … wi Wir betrachten den Zeitpunkt, zu dem wj an den Knoten v „angehängt“ wurde. Dieses Anhängen muss durch Melt geschähen sein. Somit war zu diesem Zeitpunkt der Rang von v und wj gleich und mindestens j-1 (denn die Knoten w1 bis wj-1 wurden bereits an v „angehängt“). Der Knoten wj kann seitdem maximal ein Kind verloren haben, da er sonst mittels Cut aus den Teilbaum von v geschnitten wurden wäre. Der Rang von wj ist somit mindestens j-2. Dies gilt auch für wi und somit auch für v: i-2 ≤ Rang v ≤ i Tk sei ein Baum im Fibonacci-Heap mit minimaler Anzahl an Knoten. Für Tk gilt: 1) Die Wurzel von Tk hat genau k Söhne. 2) Jeder Knoten des Baumes xi (≠ Wurzel) hat genau i-2 Söhne (für i ≥ 2, sonst 0). T0 T1 T2 T3 T4 … |T0| = 1 |T1| = 2 |Tk| = |Tk-1| + |Tk-2| Die Anzahl der Elemente in Tk ist somit gleich der k-ten Fibonacci-Zahl Fib(k). Die FibonacciZahlen lassen sich wie folgt abschätzen: Fib(k) ≥ ϕk mit ϕ = 1+ Somit gilt: |Tk| ≥ 2c*k 2 5 = 2c ■ 8 Satz: Fibonacci-Heaps unterstützen die Operationen Make, Insert, Cut und DecreaseKey in (amortisierter) Zeit O(1) und die Operation Delete in amortisierter Zeit O(ld n). Beweis: Beweis mit Hilfe der amortisierenden Analyse für die einzelnen Operationen. bi … Anzahl Bäume im Heap mi … Anzahl markierter Knoten im Heap φi … Potential mit φi = bi + 2 * mi Da der Heap Anfangs leer ist, ist das Anfangspotential φ0 = 0. Nach Definition ist das Potential stets nicht-negativ. Für die Operationen gilt: 1) Make verändert die Anzahl der Wurzeln und der markierten Knoten nicht und hat eine konstante Laufzeit O(1). 2) Insert fügt mit konstanter Laufzeit eine neue Wurzel in den Heap ein, so dass sich das Potential um O(1) erhöht. Die Anzahl der markierten Knoten verändert sich nicht. 3) Delete benötigt O(ld n). Das Minimum m wird in O(1) gefunden. Das Aufbauen eines neuen Heaps h mit den Söhnen von m benötigt O(ld n). Das Verschmelzen der Bäume im FibHeap mit h benötigt (amortisiert) keine Zeit. Das Suchen des neuen Minimums und die Transformation in eine Wurzelliste benötigt nochmals O(ld n). Die Anzahl markierter Knoten erhöht sich nicht. Die Anzahl Bäume in f erhöht sich um O(ld n). 4) Cut benötigt O(1). Das Ausschneiden eines Knotens x führt dazu dass k Knoten (1 Knoten und seine k-1 markierten Vorgänger) als neue Wurzeln in f eingefügt werden. Somit erhöht sich b um k und m wird um k-1 verringert. Zusätzlich wird der k-te unmarkierte Vorgänger von x markiert, sodass sich das Potential um O(k – 2*(k – 1) + 2) = O(1) erhöht. 5) DecreaseKey ruft maximal einmal Cut auf und benötigt somit ebenfalls O(1). ■ Bem.: Durch Verwendung eines Fibonacci-Heaps innerhalb des Dijkstra-Algorithmus kann die Laufzeit auf O(|V| * log |V| + |E|) reduziert werden. 2.4 Laufzeit-Vergleich Laufzeit für die Operationen: Make Insert Delete Cut DecreaseKey Binärer Heap Worst-case O(1) O(ld n) O(ld n) O(ld n) BinHeap Worst-case Amortisiert O(1) O(1) O(ld n) O(1) O(ld n) O(ld n) O(ld n) O(ld n) FibHeap Worst-case Amortisiert O(1) O(1) O(1) O(1) O(n) O(ld n) O(ld n) O(1) O(ld n) O(1) 9 3. Union-Find-Struktur 3.1 Motivation Ziel: Innerhalb des Kruskal-Algorithmus wird eine disjunkte Zerlegung der Kantenmenge V benötigt. Gesucht wird somit eine effiziente Darstellung der Partition P = {P1, P2, …, Pm} mit U Pi = V, Pi ∩ Pj = ∅ für i ≠ j. Bem.: Eine Möglichkeit wäre ein Feld A der Größe |V| mit A[i] = A[j] = z ⇔ E(vi, vj) ∈ Pz. Die Laufzeit für die Vereinigung zweier Teilmengen Pa und Pb ist O(|V|). Die Laufzeit des KruskalAlgorithmus ist somit O(|E| Elemente sortieren) + |V| * (# Vereinigung zweier Teilmengen) = O(|E| * log |E| + |V|2). Eine bessere Möglichkeit wird im Folgenden vorgestellt. 3.2 Union-by-Size Def.: Bsp.: Datenstruktur für Union-by-Size Gegeben sei ein Feld A der Größe n = |V| und A[i] = j wobei j der Vater (bzw. ein Vorgänger) von i ist. Falls i die Wurzel der Partitionsmenge ist gilt A[i] = i. A[1] = 1 (Wurzel) A[2] = 1 A[3] = 1 A[4] = 1 A[5] = 2 1 2 3 4 5 3.2.1 Vereinigung zweier Partitionsmengen Def.: Union(v, w) Vereinigt die Partitionsmengen Pv und Pw mit den Wurzeln v und w mittels Union-by-Size. 1) Falls Tiefe von Pv > Tiefe von Pw setze A[w] = v 2) Sonst setze A[v] = w Laufzeit: O(1) 3.2.2 Finden ohne Wegkompression Def.: Find(v) Findet die zu v gehörende Partitionsmenge ohne eine Wegkompression durchzuführen. 1) Solange A[v] ≠ v setze v = A[v] Laufzeit: O(n) Bem.: Für beliebige n-1 Vereinigungen, beginnend mit der Partition P = {{1}, {2}, …, {n}}, wird eine Laufzeit von O(n) benötigt. Die Maximale „Tiefe“ einer Partitionsmenge ist O(log n). Die Laufzeit des Kruskal-Algorithmus verringert sich damit auf O(|E| * log |E|) = O(|E| * log |V|). 10 3.2.3 Finden mit Wegkompression Def.: Find(v) Findet die zu v gehörende Partitionsmenge wobei eine Wegkompression durchgeführt wird. 1) Solange A[v] ≠ v speichere v auf den Stack und setze v = A[v] 2) Für alle Elemente w des Stacks setze A[w] = v Laufzeit: O(n) 3.2.4 Laufzeit von Union-by-Size mit Wegkompression Vor.: Gegeben seien: 1) n sei die Anzahl der Elemente in V 2) Folge von χ Union- und λ Find-Operationen σ1, σ2, …, σm mit m = χ + λ 3) Si sei die Partition nach den Operationen σ1, σ2, …, σi mit Wegkompression 4) Si’ sei die Partition nach den Operationen σ1, σ2, …, σi ohne Wegkompression 5) S0 = S0’ = {{1}, {2}, …, {n}} Es gilt: 1) v ∈ Si ⇔ v ∈ Si’ 2) w ist Wurzel von Si ⇔ w ist Wurzel von Si’ Satz: Def.: Mit Union-by-Size und Wegkompression lassen sich χ Union-Operationen und λ FindOperationen in O(χ + (n + λ) * log∗ n) auf der Startpartition S0 durchführen. Dabei sei log∗ n = min { i ∈ Ν | logi n ≤ 1 }. Für λ ~ n ist O(χ + (n + λ) * log∗ n) nahezu O(χ + λ). Level(a) Für a mit 1 ≤ a ≤ n sei Level(a) = Tiefe des Teilbaumes in Si’ mit Wurzel a. Lemma: Für A[b] = a ≠ b gilt Level(a) ≥ Level(b) + 1. Beweis: Wenn b an a „gehängt“ wird gilt Level(a) ≥ Level(b) + 1. Dieses „Anhängen“ kann nur durch Union oder bei der Wegkompression geschähen. Bei Union gilt, wegen Union-by-Size, dass a Wurzel des tieferen Teilbaumes ist. Bei „Anhängen“ durch Wegkompression muss b mittelbarer Nachfolger von a sein, und damit ebenfalls eine geringere Baumtiefe haben. Der Level von a kann sich nicht mehr verringern und da b keine Wurzel mehr ist bleibt Level(b) für alle weiteren Operationen konstant. ■ Def.: Bsp.: Niveau(a) Gegeben seinen die Niveaugrenzen Ai mit 0 = A0 < A1 < … < Ak < Ak+1 und Ak = ld n (für 0 ≤ k ≤ ld n). Für das Niveau von a gilt dann: Niveau(a) = i ⇔ Ai ≤ Level(a) < Ai+1. Zwei Möglichkeiten für k = 2 und n = 8 • A0 = 0, A1 = 1, A2 = 3, A3 = 4 • A0 = 0, A1 = 2, A2 = 3, A3 = 5 Lemma: Für i ∈ Ν und a ∈ V gilt: 1) |{ a | Level(a) = i }| ≤ n 2i 2) |{ a | Niveau(a) = i }| ≤ n 2 A i −1 11 Beweis: 1) Seien a1, a2, …, aj alle Elemente von Level i und T1, T2, …, Tj die Teilbäume mit den Wurzeln a1, a2, …, aj. Die Anzahl der Elemente von Tz (∀ z mit 1 ≤ z ≤ j) ist mindestens 2i. Da die Bäume T1, T2, …, Tj disjunkt sind gilt j * 2i ≤ n und somit j ≤ ni . 2 2) Per Definition gilt |{ a | Niveau(a) = i }| = |{ a | Ai ≤ Level(a) ≤ Ai+1 - 1}| und somit gilt: |{a|Niveau(a)=i}| = Lemma: Die Laufzeit für χ n n n n 1 1 n + Ai +1 + ...+ Ai +1 −1 = Ai 1+ + ...+ Ai +1−1−Ai ≤ Ai Ai 2 2 2 2 2 2 2 Union-Operationen und λ Wegkompression ist kleiner als c * (χ + λ * (k + 1) + ∑21 = 2 n ∞ z =0 z Ai −1 . ■ Find-Operationen bei Union-by-Size mit ∑2n (A k i=0 Ai -1 i+1 - Ai -1) ). Beweis: Wir betrachten die Laufzeit der Operationen σ1, σ2, …, σm. Falls σi eine Union-Operation ist, ist die Laufzeit von σi O(1). Andernfalls, d.h. σi ist Find(a), ist die Laufzeit von σi O(1 + # Kanten von a bis zur Wurzel in Si-1) ≤ O(log n). Wir betrachten dazu die folgenden drei Fälle: 1) a ist die Wurzel von Si-1 ⇒ O(1) 2) a ist unmittelbarer Nachfolger der Wurzel von Si-1 ⇒ O(1) 3) auf dem Weg von a zur Wurzel von Si-1 liegen die Knoten c1, c2, …cυ ⇒ O(υ + 1) Wir wissen (aus den obigen Lemmas) das Level(ci+1) ≥ Level(ci) + 1 und somit gilt: 1) Niveau(ci+1) = Niveau(ci) oder 2) Niveau(ci+1) = Niveau(ci) + 1 Gegeben seien die Knoten a = c0, c1, …, cυ, cυ+1 = b. Für die amortisierende Analyse gilt: 1) Zähle Laufzeit der Union-Operationen. 2) Für Niveau(cj) ≠ Niveau(cj+1) oder j = υ zähle die Zeit für die Kante (cj, cj+1) als „verbrauchte Zeit“. 3) Für Niveau(cj) = Niveau(cj+1) und j < υ zähle die Zeit für die Kante (cj, cj+1) als „gespeicherte Zeit“. Laufzeit: 1) χ Union-Operationen mit konstanter Laufzeit: O(χ) 2) maximal k Niveau-Übergänge und ein Übergang zur Wurzel, multipliziert mit der Anzahl FindOperationen: O(λ * (k + 1)) 3) Es bleibt zu zeigen, dass die insgesamt k n (A i +1 - A i - 1) ) ist. „gespeicherte Zeit“ O( A -1 ∑2 i= 0 i b cυ … … c i+1 c i Niveau(cj+1) Niveau(cj) … c 1 a … Lemma: Nach Ausführung der m Operationen σ1, σ2, …, σm ist die in cj „gespeicherte“ Zeit ≤ Ai+1 – Ai – 1 mit Niveau(cj) = i. Beweis: Die Anzahl der Level-Übergänge innerhalb des Niveau i ist Ai+1 – Ai – 1. Jedes Mal wenn eine Zeiteinheit in cj (mit Niveau(cj) = i) gespeichert wird, wird cj aufgrund der Wegkompression an die aktuelle Wurzel „gehängt“. Im ungünstigsten Fall ist der Level des jeweils neuen Vaters von cj immer um genau 1 größer als der Level von cj. Dann bedarf es maximal Ai+1 – Ai – 1 FindOperationen bis die Bedingung Niveau(cj) ≠ Niveau(cj+1) gilt. Ab diesen Zeitpunkt wird keine weitere Zeit in cj gespeichert, d.h. Zeit in cj ≤ Ai+1 – Ai – 1. ■ 12 Bsp.: Zu Beginn sei Si gegeben (i < j < k). Vor Sj wurde Find(e) und Union(b, c) durchgeführt. Vor Sk wurde Find(e) aufgerufen. Es werden 2 Zeiteinheiten in e gespeichert, A2 = 3 und A3 = 6. Si Sj b c Level 5 d Level 4 e Level 3 c d Sk b Level 6 c … e d … Lemma: Die insgesamt gespeicherte Zeit liegt in O( ∑ 2n k i= 0 A i -1 Niveau 3 e … Niveau 2 … (A i +1 - A i - 1) ). Beweis: Die einzelnen Elemente in Niveau i haben höchstens Ai+1 – Ai – 1 Zeiteinheiten gespeichert. In n Niveau i gibt es höchstens A −1 Elemente. Die gespeicherte Zeit innerhalb des Niveaus ist somit 2 maximal n 2 A i −1 i * (Ai+1 – Ai – 1) und für alle Niveaus gilt O( Lemma: Für Ai+1 = 2A mit A0 = 0 und 0 ≤ i ≤ k. gilt O( i Beweis: Es gilt ∑ 2n n 2 A i -1 k i= 0 A i -1 (2 Ai ) ∑ 2n k i= 0 A i -1 ∑ 2n k i= 0 A i -1 (A i +1 - A i - 1) ). ■ (A i +1 - A i - 1) ) = O(n * log∗ n). n * 2Ai = 2n und k = log∗ n. Daraus folgt die Behauptung: 2 A i -1 k n (A i +1 - A i - 1) ≤ 2n * k = 2n * log∗ n A i -1 i =1 2 - Ai -1 ≤ (A i +1 - A i - 1) = Bem.: O(χ + λ * (k + 1) + ∑ ∑ 2n k i= 0 A i -1 ■ (A i +1 - A i - 1) ) = O(χ + λ * (log∗ n + 1) + 2n * log∗ n) . Für λ ~ n und da log∗ λ nahezu konstant ist gilt O(χ + λ + (λ + 2n) * log∗ n) = O(χ + λ * log∗ λ) ≈ O(χ + λ). 13 4. Selbstorganisierende Listen 4.1 Motivation Ziel: 4.2 Das Problem DICTIONARY beinhaltet das Suchen, Einfügen und Löschen von Elementen in eine Datenstruktur (z.B. Hashtable, Suchbaum, Liste). Ziel ist eine effiziente Implementierung dieser Datenstruktur und der zugehörigen Operationen. Die Selbstorganisierende Liste Def.: 4.2.1 Def.: Selbstorganisierende Liste Eine Selbstorganisierende Liste Sn: x1 → x2 → … → xn ist eine einfach verkettete Liste mit n Elementen welche „selbständig“ die Reihenfolge der Elemente xi festlegt, d.h. durch Vertauschungen (Transpositionen) die Reihenfolge verändert. Suchen eines Eintrages in der Liste Find(x) Sucht sequentiell das Element x in der Liste und führt dabei eine gewisse Zahl von Transpositionen durch. 4.2.2 Einfügen eines Eintrages in die Liste Def.: Ins(x) Sucht sequentiell das Element x in der Liste (ob es bereits in der Liste enthalten ist), fügt x an das Ende der Liste an und führt eine gewisse Zahl von Transpositionen durch. 4.2.3 Löschen eines Eintrages in der Liste Def.: Del(x) Sucht sequentiell das Element x in der Liste, löscht dieses und führt eine gewisse Zahl von Transpositionen durch. Bem.: Unter Annahme das x in der Liste enthalten ist. 4.2.4 Heuristiken für die Transpositionen Def.: Blankly (B) Def.: Transpose (T) Def.: Frequency Counter (FC) Def.: Move to Front (MF) Führe keine Transpositionen durch. Vertausche das Element nach Einfügen oder Suchen mit seinem Vorgänger. Führe für alle Elemente eine Zähler mit. Erhöhe diesen nach Einfügen oder Suchen. Vertausche die Elemente so, dass die absteigende Sortierung der Zähler bewahrt bleibt. Bringe das Element nach Einfügen oder Suchen durch Vertauschungen an den Anfang der Liste. 14 4.2.5 Optimalität der Heuristik MF Lemma: Sei A eine beliebige Heuristik für die Transpositionen, und Pos(x) die Position von x in der Liste. Die Laufzeiten (Kosten) KA der Operationen seien dann: 1) KA(Find(x)) = Pos(x) + # Transpositionen (außer: x wird an den Anfang der Liste getauscht) 2) KA(Ins(x)) = Pos(x) + # Transpositionen (außer: x wird an den Anfang der Liste getauscht) 3) KA(Del(x)) = Pos(x) + # Transpositionen Satz: Für jede Folge von Operationen σ1, σ2,…, σn bei Anfangs leerer Liste gilt: KMF(σ) ≤ 2 * KA(σ) für ∀ A mit KMF(σ) = ∑ KMF(σi) Beweis: Gegeben sei eine Folge von Operationen σ1, σ2,…, σm und eine optimale Heuristik A. Weiter seien die Listen Sm und Sm’ (mit S0 = S0’ = ∅), welche durch die Operationen σi und die Heuristiken A und MF entstanden sind, gegeben: MF: S0 A: S0’ S1 S1’ … … Sm Sm’ Zu zeigen ist KMF(σ) ≤ 2 * KA(σ). Da dies nicht für alle Operationen σi gilt, führen wir eine amortisierende Analyse durch. Die Idee ist die Verfolgung der Inversionen von Si und Si’. Dabei sei eine Inversionen von a und b bzgl. Si und Si’ wie folgt definiert: Si: Si’: x1 → x2 → … → a → … → b → … → xn y1 → y2 → … → b → … → a → … → yn Die Anzahl Inversion bzgl. Si und Si’ ist φi = φ (Si, Si’) ≤ n mit n = |Si| ∀ i. 2 Es sei ti = K MF (σi ) und ti ’ = KA (σi ). Zu zeigen bleibt für ai = ti + φi – φi-1: Σ ti = Σ (ai – φi + φi-1) = (Σ ai) – φn + φ0 = (Σ ai) – φn ≤ Σ ai ≤ 2 * Σ ti’ 1. Fall: σ = Find(a) i Nach Voraussetzung existiert a in Si-1 an Stelle k und in Si-1’ an Stelle j, sodass gilt: Si-1: x1 → x2 → … → xk-1 → a → … → xn Si: a → x1 → … → xn Si-1’: y1 → y2 → … → yj-1 → a → … → yn Si’: y1 → … → yn Definition: ai,1 = ti + φ’ – φi-1 ai,2 = φi – φ’ ai = ai,1 + ai,2 = ti + (φ’ – φi-1) + (φi – φ’) = ti + φi – φi-1 φi-1 φ’ φi = φ (Si-1, Si-1’) = φ (Si, Si-1’) = φ (Si, Si’) φi-1 φ’ = Anzahl Inversionen welche bereits vor Find(a) da sind. = φi-1 zusätzlich der Inversionen welche durch Find(a) in Si verschwinden (= p) bzw. hinzukommen (= q). = φ’ zusätzlich der Inversionen welche durch Find(a) in Si’ hinzukommen (= r). φi 15 1) Da für alle k-1 Elemente, welche in Si-1 vor a stehen, entweder eine Inversion hinzukommt oder verschwindet gilt k – 1 = p + q bzw. q = k – p – 1. 2) Da in Si-1’ genau j-1 Elemente vor a stehen, können maximal j-1 Inversionen hinzukommen. Daher gilt q ≤ j – 1 und somit auch k – p ≤ j. 3) Weiter gilt ti = k und ti’ = j + r bzw. r = ti’ – j. ai,1 = ti + φ’ – φi-1 = k + (φi-1 + q – p) – φi-1 = k + (k – p – 1) – p = 2 * (k – p) – 1 ≤ 2 * j – 1 ai,2 = φi – φ’ = (φ’ + r) – φ’ = r = ti’ – j Für ai ergibt sich damit: ai = ai,1 + ai,2 ≤ 2 * j – 1 + ti’ – j = ti’ + j – 1 ≤ 2 * ti’ □ 2. Fall: σ = Ins(a) i Nach Voraussetzung existiert a in Si-1 und Si-1’ nicht, sodass gilt: Si-1: x1 → x2 → … → xn Ŝi-1: x1 → x2 → … → xn → a Si: a → x1 → … → xn Si-1’: y1 → y2 → … → yn Ŝi-1’: y1 → y2 → … → yn → a Si’: y1 → … → yn+1 Definition: ai,1 = ti + φ’ – φi-1 ai,2 = φi – φ’ ai = ai,1 + ai,2 = ti + (φ’ – φi-1) + (φi – φ’) = ti + φi – φi-1 φi-1 φ’ φi = φ (Si-1, Si-1’) = φ (Ŝi-1, Ŝi-1’) = φ (Si, Si’) φi-1 φ’ φi = Anzahl Inversionen welche bereits vor Ins(a) da sind. = Anzahl Inversionen welche nach dem Einfügen (vor den Transpositionen) existieren (= φi-1). = Anzahl Inversionen welche durch die Transpositionen in Ŝi-1 bzw. Ŝi-1’ hinzukommen. 1) Durch die Transpositionen innerhalb Ins(a) befindet sich a in Si an Stelle 1 und in Si’ an Stelle j. Dies verursacht j-1 (≤ n) Inversionen. 2) Weiter gilt ti = n und ti’ = n + r, wobei r eine gewisse Anzahl Transpositionen unabhängig von Ins(a) ist. D.h. durch Heuristik A werden weitere r Inversionen verursacht. ai,1 = ti + φ’ – φi-1 = ti = n ≤ ti’ ai,2 = φi – φ’ = φi = j – 1 + r ≤ n + (ti’ – n) = ti’ Für ai ergibt sich damit: ai = ai,1 + ai,2 ≤ ti’ + ti’ ≤ 2 * ti’ □ 3. Fall: σ = Del(a) i Nach Voraussetzung existiert a in Si-1 an Stelle k und in Si-1’ an Stelle j, sodass gilt: Si-1: x1 → x2 → … → xk-1 → a → … → xn Si: x1 → … → xn-1 Si-1’: y1 → y2 → … → yj-1 → a → … → yn Si’: y1 → … → yn-1 16 Definition: ai,1 = ti + φ’ – φi-1 ai,2 = φi – φ’ ai = ai,1 + ai,2 = ti + (φ’ – φi-1) + (φi – φ’) = ti + φi – φi-1 φi-1 φ’ φi = φ (Si-1, Si-1’) = φ (Si, Si-1’) = φ (Si, Si’) φi-1 φ’ φi = Anzahl Inversionen welche bereits vor Del(a) da sind. = φi-1 abzüglich der Inversionen welche durch das Löschen von a in Si-1 verschwinden (= p). = Anzahl Inversionen welche nach dem Löschen von a in Si-1’ hinzukommen (= r). 1) Es gilt ti = k und ti’ = j + r, wobei r die Anzahl der durch Del verursachten Inversionen in Si’ ist. 2) Die Anzahl Inversionen, die durch Del bzgl. Si und Si-1’ verschwinden sind p = |k – j|. ai,1 = ti + φ’ – φi-1 = k + (φi-1 – p – φi-1) = k – p = k – |k – j| ai,2 = φi – φ’ = r = ti’ – j Für ai ergibt sich damit: ai = ai,1 + ai,2 = k – |k – j| + ti’ – j ≤ |k – j| – |k – j| + ti’ = ti’ ≤ 2 * ti’ ■ 4.2.6 Nicht-Optimalität der Heuristiken B, T und FC Satz: Es existiert keine Konstante c > 0, sodass für ∀ Heuristiken A und Operationsfolgen σ gilt: 1) KB(σ) ≤ c * KA(σ) 2) KT(σ) ≤ c * KA(σ) 3) KFC(σ) ≤ c * KA(σ) Beweis: Es gilt KMF(σ) ≤ 2 * KA(σ). Es genügt daher zu Zeigen, dass die Kosten (Laufzeiten) für die Heuristiken B, T und FC größer als c * KMF(σ) sind (für ∀ c’ > 0). 1) Gegeben sei die Operationsfolgen σ = Ins(1), Ins(2), …, Ins(n), n2 × Find(n). KB(σ) = 1 + 2 + … + n + n*n2 = O(n3) KMF(σ) = 1 + 2 + … + n + 1*n2 = O(n2) □ 2) Gegeben sei die Operationsfolgen σ = Ins(1), Ins(2), …, Ins(n), n2 × (Find(1), Find(n)). KT(σ) = 1 + 2 + … + n + (n + n)*n2 = O(n3) KMF(σ) = 1 + 2 + … + n + 1 + n + 2*(n2 – 1) = O(n2) □ 3) Gegeben sei die Operationsfolgen σ = Ins(1), (n – 1) × Find(1), Ins(2), (n – 2) × Find(2), …, Ins(n). KFC(σ) = (1 + 1 * (n – 1)) + (2 + 2 * (n – 2)) + … + ((n – 1) + (n – 1) * 1) + n n n n n = (i + i * (n − i)) = (i * (n + 1) − i 2 ) = (n + 1) * i − i 2 ∑ ∑ i =1 ∑ ∑ i =1 i =1 = (n + 1) * n * (n + 1) − n * (n + 1) * (2n + 1) = 2 6 n * ( n + 1) 3 i =1 2 = O(n3) KMF(σ) = (1 + (n – 1)) + (2 + (n – 2)) + … + ((n – 1) + 1) + n = O(n2) ■ 17 5. Selbstorganisierende Bäume 5.1 Motivation Ziel: Eine weitere Form der Implementierung einer DICTIONARY-Datenstruktur ist ein binärer Baum. Dieser soll mittels Rotationen (analog zu den Transpositionen bei Listen) so verändert werden, dass er ein optimales Zugriffsverhalten aufweist. 5.2 Der Selbstorganisierende Baum Def.: Selbstorganisierender Baum Ein Selbstorganisierender Baum Tn ist ein binärer Suchbaum mit n Elementen welcher „selbständig“ seine Struktur mittels Vertauschungen (Rotationen) verändert. 5.2.1 Rotation Def.: Rot(x, y) Rotiert den Baum mit Wurzel x. Dabei wird y um x rotiert, sodass y neue Wurzel des Baumes wird. Dabei können folgende vier Fälle unterschieden werden: Fall 1 Fall 2 z y x y y T4 x T1 z x z T3 T2 T1 T1 T2 T3 T4 T2 T3 Rot(z, y) nach rechts Rot(y, x) nach rechts Rot(y, z) nach links Rot(x, y) nach links Fall 3 Fall 4 z z y x x T4 T4 x T1 T4 y z y T3 T1 T2 T3 T1 Rot(y, x) nach links Rot(x, y) nach rechts T2 T3 T4 T2 Rot(z, x) nach rechts Rot(x, z) nach links Laufzeit: O(1) 18 5.2.2 Splaying Def.: Splaying(x, T) Führt maximal zwei Rotationen im Baum T so aus, dass x entweder danach die Wurzel von T ist oder um zwei Positionen nach oben rotiert wurde. 1) Setze y = Vater von x. 2) Ist y die Wurzel von T dann Rot(y, x). Sonst 3) Setze z = Vater von y (Großvater von x). 4) Sind x und y jeweils linke bzw. rechte Söhne (Fall 1 oder Fall 2) dann Rot(z, y), Rot(y, x). 5) Andernfalls (Fall 3 oder Fall 4) Rot(y, x), Rot(z, x). Laufzeit: O(1) 5.3 Der Splay-Baum Def.: Splay-Baum Der Splay-Baum ist ein Selbstorganisierender Baum Sn mit n Elementen und den Operationen Splay, Suchen, Einfügen und Löschen. Als Elemente von Sn werden nur die inneren Knoten betrachtet, die Blätter seien daher „leer“. 5.3.1 Splay Def.: Splay(x) Rotiert x solange, bis x Wurzel des Splay-Baumes S ist. Falls x nicht im Splay-Baum ist wird das Element, welches x am „ähnlichsten“ ist, zur Wurzel des Baumes. 1) Suche x rekursiv im Splay-Baum. Ist x nicht in S enthalten, setze x = Vater des Blattes bei welchem die Suche nach x endete. 2) Solange x nicht Wurzel von S ist, Splaying(x) Laufzeit (mit n = # Aufrufe von Splaying): O(n) 5.3.2 Suchen eines Eintrages Def.: Find(x) Sucht das Element x in S mit Hilfe von Splay(x). Ist x danach gleich der Wurzel von S wurde x gefunden, andernfalls ist x nicht in S enthalten. Laufzeit (mit n = # Aufrufe von Splaying): O(n) 5.3.3 Einfügen eines Eintrages Def.: Ins(x) Fügt das Element x in den Splay-Baum S ein. 1) Rufe Find(x) auf und setze y = Wurzel von S. Falls x = y ist x bereits in S enthalten. 2) Falls x < y, hänge den linken Teilbaum von S (ebenfalls als linken Teilbaum) an x und setze y als rechten Sohn von x. 3) Falls x > y, hänge den rechten Teilbaum von S (als rechten Teilbaum) an x und setze y als linken Sohn von x. 4) Setze x als neue Wurzel des Splay-Baumes. Laufzeit (mit n = # Aufrufe von Splaying): O(n) 19 5.3.4 Löschen eines Eintrages Def.: Del(x) Löscht das Element x aus den Splay-Baum S. 1) Rufe Splay(x) auf und setze y = Wurzel von S. Falls x ≠ y, dann existiert x nicht in S. 2) Setze T = linken Teilbaum von S. Rufe Splay(x) im Baum T auf (ohne dabei S zu verändern). 3) Die Wurzel z von T ist der right-most Knoten des linken Teilbaumes von S. Entferne diesen aus S und setze z als neue Wurzel von S. x wird dabei gelöscht. Laufzeit (mit n = # Aufrufe von Splaying in S und T): O(n) 5.3.5 Die Laufzeit der Splay-Baum-Operationen Def.: Sei S ein beliebiger, binärer Suchbaum mit n Elementen (inneren Knoten). x sei ein Element von S und T der Teilbaum von S mit Wurzel x. 1) Das Einzelgewicht (individual weight) IwS(x) ≥ 1 ist eine beliebige, konstante Zahl ∈ R. 2) Das Gesamtgewicht (total weight) TwS(x) = IwS ( y ) . 3) Der Rang von x ist RS(x) = ld TwS(x) 4) Das Potential von S ist φS = RS ( y ) = ∑ y∈S ∑ y∈T ∑ ld Tw ( y) S y∈S 5) Die Laufzeit (Kosten) einer Operation σ in S sei KS(σ). Bem.: Seien x, y, z ∈ S und x, y die Söhne von z, dann gilt: 1) RS(x) ≤ RS(z) 2) RS(x) < RS(z) ⇒ ∃n mit TwS(x) < 2n ≤ TwS(z) 3) RS(x) < RS(y) ⇒ RS(x) ≤ RS(z) – 1 Wir betrachten die amortisierte Zeit ai für die einzelnen Operationen σi, wobei Si der Baum ist welcher durch σi aus Si-1 entsteht. ai = KSi-1(σi) + φSi – φSi-1 Lemma: Sei z die Wurzel von Si-1 und x’ die Wurzel von Si. Für σ i = Splay(x) gilt: ai ≤ 1 + 2 * (RSi-1(z) – RSi-1(x’)) Beweis: Für z = x’ gilt φSi = φSi-1 und KSi-1(Splay(x)) = 1. Daher gilt: ai ≤ 1 + 2 * 0 = 1. Für z ≠ x’ führt Splay(x) zu einem m-maligen Aufruf von Splaying. Für die amortisierte Zeit gilt: ai = KSi-1(σi) + φSi – φSi-1 ai = KSi-1(Splay(x)) + ∑R y∈S i Si ( y) – ∑R y∈S i −1 S i −1 ( y) Seien Si-1 = S0’, S1’, …, Sm’ = Si die Splay-Bäume welche durch Splaying entstehen. D.h. Si’ ist der Baum welcher durch i-mailges Splaying(x’) aus Si-1 entsteht. Die amortisierten Kosten für Splaying(x’) seien a1’, a2’, …, am’ mit ai = Σ aj’ und aj’ = 1 + φSj’ – φSj-1’. Wir betrachten im Weiteren das i-te Splaying. Dazu vereinbaren wir R(x) = RSi-1’(x) und R’(x) = R Si’(x) für alle Knoten x ∈ S. 20 Beim i-te Splaying können die drei folgenden Fälle unterschieden werden: 1. Fall, i = m: y, R(y) x’, R(x’) x’, R’(x’) = R(y) y, R’(y) Rot(y, x’) am’ = 1 + φSm’ – φSm-1’ = 1 + (R’(x’) + R’(y)) – (R(x’) + R(y)) am’ = 1 + R’(y) – R(x’) ≤ 1 + R’(x’) – R(x’) 2. Fall, i < m: z, R(z) y, R(y) x’, R(x’) y, R(z) = R’(x’) Rot(y, x’) Rot(z, y) x’, R(x’) z, R’(z) x, R’(x’) y, R’(y) z, R’(z) ai’ = 1 + φSi’ – φSi-1’ = 1 + (R’(x’) + R’(y) + R’(z)) – (R(x’) + R(y) + R(z)) ai’ = 1 + (R’(y) + R’(z)) – (R(x’) + R(y)) Falls R’(x’) > R(x’) gilt R’(z) ≤ R’(x’) – 1: ai’ ≤ (R’(y) + R’(x’)) – (R(x’) + R(y)) ≤ (R’(x’) + R’(x’)) – (R(x’) + R(x’)) = 2 * (R’(x’) – R(x’)) Falls R’(x’) = R(x’) gilt R(x’) ≤ R(y) ≤ R(z) = R’(x’): ai’ = 1 + (R’(x’) + R’(x’)) – (R(x’) + R(x’)) = 1 3. Fall, i < m: z, R(z) y, R(y) x’, R(x’) Rot(y, x’) z, R(z) = R’(x’) x’, R(y) y, R’(y) x’, R’(x’) ≥ R’(y) + R’(z) Rot(z, x’) y, R’(y) z, R’(z) ai’ = 1 + φSi’ – φSi-1’ = 1 + (R’(x’) + R’(y) + R’(z)) – (R(x’) + R(y) + R(z)) ai’ = 1 + (R’(y) + R’(z)) – (R(x’) + R(y)) ≤ 1 + R’(x’) – (R(x’) + R(y)) ai’ ≤ 1 + R’(x’) – (R(x’) + 1) = R’(x’) – R(x’) Somit gilt ∀j mit 0 < j < m: aj’ am’ ≤ 2 * (R Sj’(x’) – R Sj-1’(x’)) ≤ 1 + 2 * (R Sm’(x’) – R Sm-1’(x’)) Für ai = Σ aj’ gilt daher: ai ai ≤ 1 + 2 * (R S1’(x’) – R S0’(x’) + R S2’(x’) – R S1’(x’) + … + R Sm’(x’) – R Sm-1’(x’)) ≤ 1 + 2 * (R Sm’(x’) – R S0’(x’)) = 1 + 2 * (R Si-1(z) – R Si-1(x’)) ■ 21 Satz: Jede Folge von Splay-Baum-Operationen σ1, σ2,…, σm bei Anfangs leerem Splay-Baum S0 hat eine Laufzeit von O(m * log n), wobei n die maximale Anzahl von Elementen im Splay-Baum ist. Beweis: Sei S ein Splay-Baum mit n Elementen und es gilt IwS(x) = 1 ∀x ∈ S. Daher gilt: 1) TwS(x) = n 2) RS(x) ≤ ld n Die Splay-Baum-Operationen Find, Ins und Del haben eine konstante Anzahl Splay-Aufrufe. Amoritisiert betragen die Kosten pro Splay-Aufruf: KS(Splay(x)) ≤ ai ≤ 1 + 2 * (R Si-1(z) – R Si-1(x’)) ≤ 1 + 2 * (ld n – 1) ≤ 2 * ld n Die Kosten für m Operationen sind daher O(m * KS(Splay(x))) = O(m * ld n). ■ 5.3.6 Lernfähigkeit von Splay-Bäumen Satz: Gegeben sei eine Folge σ von Find-Operationen σ1, σ2,…, σm und ein optimaler Suchbaum S (ausgeglichen) mit n Elementen. S0 sei ein beliebiger Suchbaum, wobei gilt x ∈ S ⇔ x ∈ S0. Für m ≥ n ist KS0(σ) ≤ c * KS(σ). Beweis: Die Kosten für den statischen Suchbaum S sind: KS(σ) = ∑ (depth( x ) + 1) = O(m * ld n) m i =1 i Die Kosten für den Suchbaum S0 mit Splay-Operationen sind: KS0(σ) = ∑ (depth( x ) + 1 + ld * n ) + n * ld * n = O(m * ld n) + O(n * ld n) = O(m * ld n) m i =1 i ■ 22 6. Diskrete Fourier-Transformation 6.1 Grundlagen Bem.: Sei z = x + i y mit x, y ∈ R, z ∈ C. Folgende Aussagen gelten (ohne Beweis): 1) Geometrisch interpretiert sind x und y Polarkoordinaten derart x = r cos ϕ und y = r sin ϕ mit r = x 2 + y 2 und ϕ = arg z. Demnach gilt z = r cos ϕ + i r sin ϕ = r (cos ϕ + i sin ϕ). 2) Mithilfe der geometrischen Reihen von cos, sin und ex lässt sich zeigen, dass ez = ex (cos y + i sin y). 3) Für z = r (cos ϕ + i sin ϕ) gilt z = e mit z’ = ln r + i ϕ. 4) Für z = r e ϕ1 und z = r e ϕ2 gilt z * z = r * r e ϕ1 ϕ2 . z’ 1 1 i 2 i 2 1 2 1 2 i ( Def.: Einheitswurzel Sei ω n ∈ C und n ∈ N. Dann gilt: ω n Satz: Es gibt genau n verschiedene n-te Einheitswurzeln ω n ∈ C. 2π i (d.h. k’ = k mod n) und somit ωnk Satz: =e =e ) ist die n-te Einheitswurzel ⇔ ω nn = 1. Beweis: Gegeben sei ωn = e n . Dann gilt ωnn = e schiedene n-te Einheitswurzeln ωnk + 2π i k n 2π i n n = e 2π i = cos 2π + i sin 2π = 1 . Es existieren n ver- für 0 ≤ k < n. Für k ≥ n gilt k = m * n + k’ mit 0 ≤ k’ < n 2π i n*m n e 2π i k' n = 1m * e 2π i k' n = ωnk ' . ■ Die n-ten Einheitswurzeln bilden bzgl. der Multiplikation eine Gruppe. Beweis: Gegeben seien zwei Einheitswurzeln ω nj und ω nk . Dann gilt ωnj * ω nk = ω nj +k = ωn( j+k ) mod n . Das neutrale Element ist ω n0 = 1 und da ωnk * ωnn−k = ω nn = ωn0 , ist ωnn−k die Inverse von ω nk bzgl. der Multiplikation. ■ Lemma: Cancellation-Lemma Gegeben sei die Einheitswurzel ω dndk mit d, n ∈ N und 0 ≤ k < n. Es gilt ωdndk n n Somit gilt für gerade n ∈ N die Gleichung ωn2 = ω n2*1 = ω21 = e *2 2π i *1 2 =e 2π i dk dn =e 2π i k n = ω nk . = eπ i = −1. 2 Lemma: Halbierungslemma Für gerade n sind n −1 (ω n0 ) 2 , (ωn1 ) 2 ,..., (ω n 2 ) 2 die Lemma: Summationslemma Für n ∈ N und k ≠ Vielfaches von n gilt Def.: Fourier-Matrix Die Matrix F n ∑ n −1 j =0 n -ten Einheitswurzeln, denn es gilt (ωnk ) 2 = ω22**nk = ω nk . 2 2 2 (ω nk ) j = 1 − (ω nk ) n 1 − (ωnn ) k 1 − 1k = = = 0. 1 − ω nk 1 − ωnk 1 − ωnk = [(ωni ) j ]in,−j1=0 heißt Fourier-Matrix. F ist eine quadratische, symmetrische und n invertierbare Matrix mit F n ∈ Cn×n. Die Inverse von F ist F = [ 1 (ωni ) − j ]in,−j1=0 , denn F -1 n n n n F n -1 = I. 23 6.2 Fast Fourier-Transformation Ziel: Ziel ist die Berechnung von y = F x in O(n * ld n), wobei x, y ∈ R und n = 2m (n, m ∈ N). Idee: Für x = (x0, x1, …, xn-1)T und y = (y0, y1, …, yn-1)T gelte y = Fn x. Somit gilt yk = n n ∑(ω ) x . n −1 j =0 Weiter sei x(0) = (x0, x2, x4, …, xn-2)T und x(1) = (x1, x3, x5, …, xn-1)T. Für yk mit 0 ≤ k < n 2 n −1 2 n −1 2 ∑(ω ) x = ∑(ω ) yk = Fn x (0) + ω nk Fn x (1) = y k(0) + ω nk y k(1) k n j =0 j j 2 k n j =0 ∑ j =0 j j =0 k ( 2 j +1) n x(2 j +1) = n −1 2 ∑(ω ) x k n 2 j =0 j 2j +ω k n n −1 2 ∑(ω ) x j =0 k n 2 j ( 2 j +1) 2 n 2 yk’ = ∑(ω ) x2 j + 2j Für yk’ mit k’ = + k und 0 ≤ k < n −1 j gilt dann: yk = n −1 k n (ωnk ' ) j x j = n −1 2 n −1 2 ∑ j =0 n 2 (ωnk ' ) 2 j x2 j + n −1 2 n −1 2 ∑ j =0 yk’ = ∑(ω ) x yk’ = Fn x (0) − ωnk Fn x (1) = y k(0) − ω nk yk(1) j =0 k n 2 j 2j 2 − ωnk ∑(ω ) x gilt dann: j =0 k n 2 j (ωnk ' ) (2 j +1) x(2 j +1) = n −1 2 ∑ j =0 n 2 n 2 n 2 n (ω ω nk ) j x2 j + ω ωnk 2 n −1 2 ∑ j =0 n (ω n2 ω nk ) j x( 2 j +1) 2 2 ( 2 j +1) 2 6.2.1 Der Algorithmus FFT Def.: FFT(x, n) Berechnet y = Fn x für ein gegebenes x ∈ Rn und n = 2m rekursiv. 1) Falls n = 1 gebe x0 zurück. Sonst 2) Erzeuge x(0) und x(1) mit x(0) = (x0, x2, x4, …, xn-2)T und x(1) = (x1, x3, x5, …, xn-1)T. 3) Setze y(0) = FFT(x , n ) und y(1) = FFT(x , n ). (0) 4) Gebe y = (y0, y1, …, (1) 2 yn-1)T 2 mit yk = y + ω y k(1) zurück. (0) k k n Laufzeit: O(n * ld n) Def.: iFFT(x, n) Berechnet y = F x für ein gegebenes x ∈ Rn und n = 2m rekursiv. 5) Falls n = 1 gebe x0 zurück. Sonst 6) Erzeuge x(0) und x(1) mit x(0) = (x0, x2, x4, …, xn-2)T und x(1) = (x1, x3, x5, …, xn-1)T. 7) Setze y(0) = iFFT(x , n ) und y(1) = iFFT(x , n ). n -1 (0) (1) 2 ( 2 ) 1 8) Gebe y = (y0, y1, …, yn-1)T mit yk = y k(0) + ωnn− k y k(1) zurück. n Laufzeit: O(n * ld n) 24 6.2.2 Die Punkt-Wert-Darstellung Def.: Satz: Punkt-Wert-Darstellung Die Punkt-Wert-Darstellung eines Polynoms p(x) vom Grad n – 1 ist eine Menge von n Paaren ( ω n0 , y0), ( ω 1n , y1), ( ωn2 , y2), …, ( ω nn−1 , yn-1) für die gelten yj = p( ω nj ). Für jede Punkt-Wert-Darstellung existiert ein eindeutiges Polynom p(x). Beweis: Es gilt p( ωnj ) = ∑(ω ) x für 0 ≤ j < n und somit y = F x. Da F eindeutig ist, ist auch y und n −1 k n j =0 j n j n somit auch p(x) eindeutig. ■ 6.2.3 Polynommultiplikation mittels Fourier-Transformation Def.: Mult(a, b) Berechnet das Produkt der Polynome p und q mit p(x) = an x + a x + … + a x + a und q(x) = b x + b x + … + b x + b . Die Koeffizienten des neuen Polynoms r = p * q seien c , c , …, c . Der Rückgabewert ist c = (c , c , …, c ) mit c ∈ R . 1) Setze a’ = (a , a , a , …, a , 0, …, 0) und b’ = (b , b , b , …, b , 0, …, 0) mit a’, b’ ∈ R . 2) Berechne mittels der Fourier-Transformation y = FFT(a’, 2n – 1) und y’ = FFT(b’, 2n – 1). 3) Setze z = (y y , y y , …, y y ) . 4) Berechne mittels der inversen Fourier-Transformation die Koeffizienten c = iFFT(z, 2n – 1). -1 n-1 0 1 n-1 n-2 n-2 1 1 2n-2 0 0 1 0 ’ 1 T n-1 1 ’ 2n-2 2n-2 n-2 n-2 1 1 0 0 0 2 n-1 1 0 2n-2 1 T 2 2n-1 n-1 T 2n-1 ’ T Laufzeit: 1) Bestimme a’ und b’: O(n) 2) Berechne y und y’ mittels FFT: O(2 * (2n – 1) * ld (2n – 1)) 3) Bestimme z: O(n) 4) Berechne c mittels inverser FFT: O(2(n – 1) * ld 2(n – 1)) 1-4) O(n + 3 * (2n – 1) * ld (2n – 1) + n) = O(n * ld n) 25 7. Average-Case für das Sortieren 7.1 Grundlagen Def.: Wahrscheinlichkeitsraum Ein Wahrscheinlichkeitsraum Ωn ist eine Menge mit n Elementen ω ∈ Ω für die gilt: 1) P[ω ] ∈ [0, 1] 2) P[ω ] = 1 i ∑ i ω∈Ω n 3) Def.: ∀A ⊆ Ω n ist P[A] = ∑ P[ω ] ω∈A Zufallsvariable Eine Zufallsvariable ist eine Abbildung der Form X: Ω → R, d.h. X(ω ) = y ∈ R. i Def.: Erwartungswert Der Erwartungswert einer Zufallsvariablen X ist E(X) = ∑ P[X = i] * i ∑ P[ω ] * X(ω ) . Für Ω ω∈Ω n n gilt E(X) = . Für gleichverteilte Wahrscheinlichkeiten gilt E(X) = n = {1, 2, …, n} ∑ E[X ] ω∈Ω n i =1 ω . 7.2 Untere Schranke für den Average-Case Vor.: Gegeben sei der Wahrscheinlichkeitsraum Ω mit: 1) Ω = { (a1, a2, …, an) | ai ∈ {1, 2, …, n} und ai = aj ⇔ i = j } 1 2) P[ωi] = n! 3) Zufallsvariable Tn(ωi) = Zeit für Sortieren von ωi Satz: Der Average-Case für das Sortieren einer n-elementigen Menge ist gerade der Erwartungswert von Tn mit E(Tn) = ∑ T n(ω! ) . Eine untere Schranke von E(T ) ist Ω(n * ld n). n n ω∈Ω Beweis: Wir betrachten den Entscheidungsbaum für das Sortieren einer n-elementigen Menge. Die Anzahl von Elementen in Ω ist n!, sodass der Entscheidungsbaum gerade n! Blätter besitzt. Tn(ω) entspricht dabei der Tiefe des Blattes ω im Entscheidungsbaum. Der Ausdruck Tn (ω ) ist somit die Summe aller Blatttiefen. Diese ist minimal, wenn der ∑ ω∈Ω Entscheidungsbaum vollständig ausgeglichen ist. Die Tiefe der Blätter ist dann mindestens ld n!. Somit gilt: E(Tn) = ∑ T n(!ω) ≥ n1! ∑ ld n! = ld n! ≥ n2 * ld n2 = Ω(n * ld n). n ω∈Ω ω∈Ω ■ 26 7.3 Obere Schranke für den Average-Case Satz: Eine obere Schranke von E(Tn) ist O(n * ld n). Beweis: Die Worst-Case-Laufzeit von diversen Sortieralgorithmen (wie bspw. HeapSort) ist O(n * ld n). Dies ist somit auch eine obere Schranke für den Average-Case. ■ Bem.: Da sowohl die untere, als auch die obere Schranke für den Average-Case O(n * ld n) ist, ist auch der der Average-Case in O(n * ld n). 7.4 Der Average-Case für QuickSort Vor.: Gegeben sei der Wahrscheinlichkeitsraum Ω mit: 1) Ω = { (a , a , …, a ) | a ∈ {1, 2, …, n} und a = a ⇔ i = j }, wobei ω [j] = a für 1 ≤ j ≤ n 2) P[ω ] = 1 n! 3) Zufallsvariable X (ω ) = # Vergleiche und Transpositionen innerhalb von QuickSort n n 1 2 n i i j i j i n Satz: i Der Average-Case für das Sortieren einer n-elementigen Menge mit QuickSort ist der X n (ω ) Erwartungswert von Xn mit E(Xn) = . Dieser liegt in O(n * ln n). ∑ n! ω∈Ω Beweis: Wir betrachten zunächst eine Liste ω ∈ Ωn mit ω[1] = 1, d.h. das kleinste Element steht zu Beginn und wird als Pivot-Element ausgewählt. Dann gilt für ω’’ = (ω[2], ω[3], …, ω[n]): Xn(ω) = n + 1 + Xn (ω’’) -1 Allgemein gilt für ω ∈ Ω mit ω[1] = k, ω’ = (ω[1], ω[2], …, ω[k-1]) und ω’’ = (ω[k+1], …, ω[n]): n X (ω) = n + 1 + X (ω’) + X (ω’’) n k-1 n-k Gegeben seien ω und ω mit: 1 ω ω 1 2 2 = (ω[1], ω[2], …, ω[k-1], k, ω[i ], ω[i ], …, ω[i ]) = (ω[1], ω[2], …, ω[k-1], k, ω[j ], ω[j ], …, ω[j ]) 1 2 n-k 1 2 n-k Da X nur von den ersten k-1 Stellen von ω abhängt, gilt für ω ’ = ω ’ = (ω[1], ω[2], …, ω[k-1]): 1 k-1 2 X (ω ’) = X (ω ’) 1 k-1 2 k-1 Die Anzahl solcher Elemente für die gilt ω [i] = ω[i] für 1 ≤ i < k ist gerade (n – k)!. Weiter gilt dass die Reihenfolge der ersten k-1 Elemente aller ω beliebig ist, d.h. für alle i < k existiert j < k z z sodass gilt ω [i] = ω[j]. Die Anzahl dieser Elmente ist z für alle diese ω gleich ist gilt X (ω ’) = X (ω ) * z k-1 z k-1 n −1 n − k analog für ω’’ mit X (ω ’’) = X (ω ) * n-k z n-k * * n k n k − 1 − 1 − 1 − 1 * (n – k)!. Da die Zufallsvariable * (n – k)!, wobei ω * * (k – 1)! mit ω * ∈Ω n-k ∈Ω k-1 . Dies gilt . 27 Für den Erwartungswert E (unter der Bedingung dass k am Anfang der Liste ω steht) gilt somit: k ∑ X n(!ω) = n1! ∑ (n + 1 + X (ω ' ) + X (ω' ')) n −1 n −1 1 * (k - 1)! E (X ) = n + 1 + 1 ∑ X (ω ) * X (ω ) * * (n - k)! + ∑ n−k n! k −1 n! 1 + ∑ X (ω ) * E (X ) = n + 1 + ∑ X (ω ) * 1 (k − 1)! (n − k )! E (X ) = k k n k n E (X ) = k n n n ω∈Ω n ω ∗∈Ω k −1 ω ∗∈Ω k −1 n +1+ k -1 ω∈Ω n n -k ∗ k -1 ω ∗∈Ω n − k ∗ k -1 ω ∗∈Ω n − k ∗ n -k ∗ n -k E (X ) + E (X ) k-1 k n-k k Die Bedingung, dass k am Anfang der Liste ω steht, wurde noch nicht berücksichtigt. Dies wird im Folgenden nachgeholt. Die Wahrscheinlichkeit dafür, dass k am Anfang der Liste ω steht, ist P[k] = den Erwartungswert: E(X ) = ∑ E(Xn) = n +1+ n E(Xn) = n i =1 P[i] * E i (X n ) = 1 n 2 n +1+ n ∑ 1n * (n + 1 + E (X n i i =1 ∑ (E (X ) + E (X ∑ E (X ) i -1 1 . n Somit gilt für ) + E i (X n -i )) n i i =1 n i i =1 i-1 i n -i )) i-1 Es lässt sich zeigen, dass somit gilt: E(Xn) = n +1 + E( X n -1 ) + 2 ≤ n n ∑ ni ++11 * 3 n i=2 1 ≤ 3 * (n + 1) * i 1 + i=2 E(Xn) ≤ 3 * (n + 1) * ∑ E(Xn) = O(n * ln n) n +1 ∫ 2 1 di = 3 * (n + 1) * (ln (n + 1) - 1) i ■ 28