Theoretische Informatik III - Website von Michael Brückner

Werbung
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
Herunterladen