2.7 Datenstrukturen für Intervalle

Werbung
2.7 Datenstrukturen für Intervalle
Grundmenge {1, . . . , n}.
Intervalle sind Teilmengen vom Typ {i, . . . , j}, 1 ≤ i ≤ j ≤ n.
Sie spielen eine besondere Rolle, z. B. bei Bildverarbeitung,
Geometrie, Genomsequenzen in der Molekularbiologie, . . .
Problemstellung:
n fest vorgegeben, i → wi , „◦“ eine assoziative Verknüpfung
wie „+“, „∗“, „min“, . . . hier o. B. d. A. „+“.
Unterstützt werden sollen:
query(i, j) → w(i, j) := wi + · · · + wj .
update(i, a) → je nach Bedarf wi := a oder wi := wi + a.
. – Seite 167/726
Zwei naive Lösungen
– Array der Länge n für wi an Position i.
Platz Θ(n), update Zeit O(1), query Zeit O(n),
preprocessing Zeit (Vorbereitung der Datenstruktur)
O(n).
Die meisten Intervalle erfordern query Zeit Θ(n).
– Matrix der Größe n × n für w(i, j) an Position (i, j), i ≤ j ,
0 sonst.
Platz Θ(n2 ), update Zeit Θ(n2 ), query Zeit O(1),
preprocessing Zeit Θ(n2 ).
Beachte: i ist in i · (n − i + 1) Intervallen enthalten.
. – Seite 168/726
Segmentbäume (segment trees)
[l, r]
allgemein
[1,20]
[l, ⌊(l + r)/2⌋]
[11,20]
[1,10]
[1,5]
[1,3]
[6,10]
[4,5]
[⌊(l + r)/2⌋ + 1, r]
[6,8]
[11,15]
[9,10]
[11,13]
[16,20]
[14,15]
[16,18]
[19,20]
[1,2] [3,3] [4,4] [5,5] [6,7] [8,8] [9,9] [10,10] [11,12] [13,13] [14,14] [15,15] [16,17] [18,18] [19,19] [20,20]
[1,1] [2,2]
[6,6] [7,7]
[11,11] [12,12]
[16,16] [17,17]
. – Seite 169/726
Level k : Abstand k von der Wurzel.
(Erinnerung ⌈⌈n/2⌉/2⌉ = ⌈n/4⌉, ⌊⌊n/2⌋/2⌋ = ⌊n/4⌋, also
⌊n/4⌋ ≤ ⌈⌊n/2⌋/2⌉, ⌊⌈n/2⌉/2⌋ ≤ ⌈n/4⌉ ).
Also Intervalle auf Level k haben ⌊n/2k ⌋ oder ⌈n/2k ⌉
Elemente.
Falls ⌈n/2k ⌉ ≥ 2, wird das größte Intervall halbiert.
Das größte k mit ⌈n/2k ⌉ ≥ 2 ist k = ⌈log n⌉ − 1.
Also: Tiefe des Segmentbaums für [1, n] ist ⌈log n⌉.
. – Seite 170/726
Binäre Bäume mit n Blättern haben n − 1 innere Knoten und
somit 2n − 1 Knoten und 2n − 2 Kanten.
Induktionsbeweis:
n = 1 (klar).
n − 1 → n: Sei T binärer Baum mit n Blättern, v und w
Geschwisterblätter.
u
v
w
Entferne v und w → u wird Blatt → n − 1 Blätter.
Rest hat (Indvss.): n − 2 innere Knoten, also hat T n − 1 innere
Knoten, n Blätter, zusammen 2n − 1 Knoten und 2n − 2 Kanten,
da Ingrad 1 für alle Knoten mit Ausnahme der Wurzel.
Speicherplatz O(n).
. – Seite 171/726
Preprocessing
– Baue Baum ohne w(i, j)-Werte top-down, z. B. Inorder.
– Speichere wi am Blatt [i, i].
– Berechne alle w(i, j) mit Postorder-Traversierung.
O(n)
. – Seite 172/726
Update (i, a)
– Starte an der Wurzel und suche das Blatt [i, i],
O(log n)
speichere Weg auf einem Stack.
– Ändere den Wert an [i, i].
O(1)
– Gehe den Weg rückwärts und berechne die Werte auf
diesem Weg neu
(alle anderen Werte bleiben aktuell).
O(log n)
O(log n)
. – Seite 173/726
query (i, j)
1. Ansatz
[k, l]
w1
v1
′
v2
w2
v2
w3
v3 w3′
′
′
v4
v4 w 4 w 4
v5 ′
w5′ w5
v6
v6
w6
[i, i]
[j, j]
Suche Wege
zu [i, i] und
[j, j],
speichere
die
Wege ab dem
Gabelungspunkt [k, l].
. – Seite 174/726
Gabelungspunkt gehöre zu Intervall [k, l].
Stets gilt: [i, j] ⊆ [k, l].
[k, l]
v1
.
.
.
.
.
.
.
[i, i]
v2
Alle Intervalle [a, b] in T (v1 )
haben die Eigenschaft b ≤ j .
Falls zusätzlich i ≤ a, können
sie für w(i, j) benutzt werden.
[j, j]
T (v1 )
Weg zu [i, i] geht nach links →
Verwende Informationen an w.
u
w
v
u
v
w
Weg zu [i, i] geht nach rechts
→ Verwende Informationen an
v nicht.
. – Seite 175/726
Verwende schließlich die Information an [i, i].
Auf dem Weg zu [j, j] spiegelbildlich analog.
Jedes wm mit i ≤ m ≤ j wird genau einmal verwendet.
Wege zu [i, i] und [j, j] enthalten zusammen max.
2⌈log n⌉ + 1 Knoten.
Pro Ebene wird auf jedem Weg maximal ein Extraknoten
betrachtet.
Arbeit pro Knoten: O(1), also insgesamt O(log n).
. – Seite 176/726
Ansatz 2
Ansatz 1 + Verbesserung:
Ist an der Gabelung [k, l] = [i, j], verwende Information dort
und stoppe.
Wird in T (v1 ) ein Intervall [a, b] mit a = i erreicht, verwende
Information dort und stoppe.
. – Seite 177/726
query(i, j):
Aktuell erreichter Knoten gehöre zu Intervall (k, l).
Starte mit (k, l) := (1, n).
Phase 1:
Fall 1 j ≤ ⌊(k + l)/2⌋, Gabelungspunkt noch nicht erreicht,
nur links suchen, l := ⌊(k + l)/2⌋.
Fall 2 i ≥ ⌊(k + l)/2⌋ + 1, Gabelungspunkt noch nicht erreicht,
nur rechts suchen, k := ⌊(k + l)/2⌋ + 1.
Fall 3 (k, l) = (i, j). Ausgabewert steht an diesem Knoten.
STOP.
Fall 4 NOT(Fall 1 OR Fall 2 OR Fall 3), d. h.
i ≤ ⌊(k + l)/2⌋ < j , (k, l) 6= (i, j)
Gabelungspunkt erreicht.
Starte Phase 2 mit (k, ⌊(k + l)/2⌋) und Phase 3
mit (⌊(k + l)/2⌋ + 1, l).
. – Seite 178/726
Phase 2:
Stets gilt i ≤ k ≤ l ≤ j .
Fall 1 i ≥ ⌊(k + l)/2⌋ + 1, alle Informationen dieses
Teilbaumes im rechten Teilbaum, k := ⌊(k + l)/2⌋ + 1.
Fall 2 i = k , benutze w(k, l) für Gesamtergebnis, STOP.
Fall 3 i > k , aber nicht Fall 1, benutze alle Informationen
im rechten Teilbaum, also w(⌊(k + l)/2⌋ + 1, l),
für das Gesamtergebnis und suche Restinformationen
im linken Teilbaum, l := (⌊(k + l)/2⌋).
Phase 3:
Spiegelbildlich analog.
. – Seite 179/726
Zusammenfassung
alle wi
Speicherplatz
Θ(n)
Preprocessing Θ(n)
update(i, a)
Θ(1)
query(i, j)
O(n)
alle Intervalle Segmentbaum
Θ(n2 )
Θ(n)
Θ(n2 )
Θ(n)
O(n2 )
Θ(log n)
Θ(1)
O(log n)
. – Seite 180/726
2.8 Datenstrukturen für Partitionen
Grundmenge {1, . . . , n}.
Zu jedem Zeitpunkt gehört i genau einer Teilmenge an,
einer Firma, einer Zusammenhangskomponente, . . .
Es gibt aber Fusionen.
Zu unterstützende Operationen:
FIND(i) → Name der Teilmenge, in der sich
i zu diesem Zeitpunkt befindet.
UNION(A,B) → Vereinigung der Mengen A und B; Name
der Vereinigungsmenge frei wählbar, solange
die Namen aktueller Mengen verschieden
sind, A und B werden eliminiert.
. – Seite 181/726
Zwei naive Lösungen
– Array der n Elemente, an Position i der Name
der Menge, die i enthält.
Speicherplatz O(n), Preprocessing O(n),
FIND O(1), UNION O(n).
Sequenz von m FIND- und n − 1
UNION-Befehlen O(n2 + m).
. – Seite 182/726
– Array für n Listenanfänge, die die aktuellen
Elemente der Menge enthalten, zusätzlich size-Wert
für jede Liste,
zu Beginn: Menge i enthält genau i,
UNION: kleinere Menge auflösen, Liste durchlaufen
und Elemente in Liste der größeren Menge einfügen.
size-Werte addieren.
Speicherplatz O(n), Preprocessing O(n), FIND O(n)
UNION O(min(size(A), size(B))).
Sequenz von m FIND- und n − 1 UNION-Befehlen
O(nm + Zeit für UNION).
. – Seite 183/726
Datenstrukturen mit besonders schnellen FINDs
Kombiniere die beiden naiven Datenstrukturen
3
1
7
3
3
1
7
3
1
1
3
7
Array mit FIND(i)
4
0
5
0
0
0
3
0
0
0
0
0
Array der Mengen mit size-Variable
Speicherplatz O(n)
2 nil 5 nil nil nil 12 nil nil nil nil nil
6
4
3
10
11
7
9
1
nil
nil
8
Preprocessing O(n)
FIND O(1)
nil
. – Seite 184/726
UNION(A, B)
– Teste, ob size(A) ≤ size(B).
(Im Folgenden nur der Fall „Ja“, der andere Fall analog.)
– Durchlaufe L(A):
für alle i: FIND(i) := B ,
entferne i aus L(A),
füge i in L(B) ein.
– size(B) = size(A) + size(B), size(A) := 0.
O(size(A))
m FIND-Befehle und n − 1 UNION-Befehle:
O(m + Kosten
UNIONs})
|
{z
O(n2 )
bessere Abschätzung?
. – Seite 185/726
1
2
3
Kosten nur O(n)
4
..
.
n
n-1 n=2k
1 2 3 4
......
balancierter Baum
Gesamtkosten
→ n/2 UNION, Kosten 1
→ n/4 UNION, Kosten 2
→ n/2
→ n/2
→ n/2i UNION, Kosten 2i−1
→ n/2
→ n/2k UNION, Kosten 2k−1 = n/2 → n/2
(n/2) · log n
= O(n log n).
. – Seite 186/726
Kosten der UNION-Befehle O(n log n)
Seien A1 , . . . , An−1 die jeweils kleineren Mengen der
UNION-Befehle
→ Kosten |A1 | + · · · + |An−1 |,
Anzahl der Summanden: n − 1, Größe der Summanden:
mind. 1, max. n/2, schwer abzuschätzen.
Ausweg: Buchhaltermethode
Buche die Kosten auf andere Kostenträger um, addiere
dann.
Neue Kostenträger: 1, . . . , n.
i-ter UNION-Befehl mit Kosten |Ai | gibt je eine
Kosteneinheit an j ∈ Ai .
. – Seite 187/726
Wie viele Kosten kann ein Kostenträger k maximal
erhalten?
Kosteneinheit an k →
k bei Vereinigung in der kleineren Menge →
die neue Menge ist mindestens doppelt so groß →
bei s Kosteneinheiten enthält die zugehörige Menge am
Ende mindestens 2s Elemente →
2s ≤ n
→
s ≤ ⌊log n⌋
→ Gesamtkosten ≤ n · ⌊log n⌋.
. – Seite 188/726
Datenstruktur für besonders schnelle UNIONs
Mengen sind Bäume mit Zeigern auf Elter.
Mengenname
und -größe
Array E (Elter), indiziert mit Elementnummern 1, . . . , n:
Für i Verweis auf Elter; falls nil: Mengenname und -größe.
Array M (Menge), indiziert mit Mengennamen aus 1, . . . , n:
Für j entweder nil für ungültig, oder auf den Knoten, der der
Wurzel der Menge entspricht.
. – Seite 189/726
Initialisierung:
i in Menge i, d. h.
E(i) = (nil, i, 1)
M (i) = i
Mengenname
Nummer der Wurzel der Menge i
Mengengröße
Speicherplatz O(n)
Preprocessing Zeit O(n)
UNION(i, j )
– M (i) und M (j) berechnen,
– sei E(M (i)) = (nil, i, s1 ),
E(M (j)) = (nil, j, s2 ),
– falls s1 ≤ s2 :
E(i) := j , M (i) := nil
E(j) := (nil, j, s1 + s2 ), M (j) := j
sonst analog.
O(1)
. – Seite 190/726
FIND(i)
– Starte an i und suche über die Elterzeiger die Wurzel,
wo der Mengenname steht.
O(Tiefe des Baumes)
Lemma: Bei n Daten haben die Bäume maximal Tiefe
⌊log n⌋.
Beweis: Behauptung: Tiefe d ⇒ mind. 2d Knoten.
Bei Tiefe ⌊log n⌋ + 1 wären das mindestens 2⌊log n⌋+1 > n
Knoten, Widerspruch.
Beweis der Behauptung durch Induktion über d.
d = 0 : Tiefe 0 → 1 Knoten X
. – Seite 191/726
d − 1 → d:
Sei T ein Baum mit Tiefe d und kleinster Knotenzahl.
Wie entstand T ?
Also size(T1 ) ≤ size(T2 ) < size(T1 )+ size(T2 )
T1
T2
= size(T ).
T
depth(T1 ) ≤ d − 1, da depth(T ) = d.
Falls depth(T1) < d − 1, dann depth(T2 ) = d und T2 hat Tiefe
d und weniger Knoten als T , Widerspruch.
Somit depth(T1 ) = d − 1 und (Indvor.) size(T1 ) ≥ 2d−1 .
Auch size(T2 ) ≥ size(T1 ) ≥ 2d−1 und
size(T ) = size(T1 )+ size(T2 ) ≥ 2d−1 + 2d−1 = 2d .
. – Seite 192/726
Effizienzsteigerung durch Pfadkomprimierung (path
compression)
Etwas mehr Aufwand bei FIND-Operationen als nötig, um
spätere FIND-Operationen zu beschleunigen.
FIND(i)
– Starte an i und suche über den Elterzeiger die Wurzel,
wo der Mengenname steht.
Speichere alle dabei gefundenen Knoten (z. B. Stack).
– Wähle für alle Knoten auf dem Suchpfad die Wurzel als
neuen Elter.
. – Seite 193/726
v0
v0
v1
v1
T0
T0
v2
T1
T1
vk = x
v2
T2
Tk
vk = x
T2
Tk
Aufwand pro Knoten in etwa verdoppelt, aber Suchwege für
viele Knoten verkürzt.
Analyse? Schwierig.
. – Seite 194/726
Funktion Zweierturm: Z(0) := 1, Z(i) := 2Z(i−1) .
Z(1) = 2, Z(2) = 4, Z(3) = 16, Z(4) = 65536, Z(5) = 265536 .
(Für jede Zahl z mit realem Bezug zu Anzahlen von
Objekten, Zeitschritten, ... gilt z ≤ Z(5).)
Funktion Log-Stern: log∗ n = min{k|Z(k) ≥ n}.
z. B. log∗ 2000 = 4, log∗ 105 = 5, log∗ 10100 = 5.
Also: limn→∞ log∗ n = ∞, aber log∗ n wächst ganz, ganz
langsam.
Satz
Zeit für m FIND- und n − 1 UNION-Operationen
O((n + m) log∗ n).
. – Seite 195/726
Herunterladen