6 ABFRAGEN ORTHOGONALER BEREICHE 6 6.1 Abfragen orthogonaler Bereiche 1-dimensionale Bereichsabfragen Wir suchen für eine feste Menge P := {p1 , . . . , pn } und variable Intervalle [x, x′ ] alle Elemente von P ∩ [x, x′ ]. Dafür wählen wir einen balancierten binären Suchbaum T als Datenstruktur für P . Die Blätter von T enthalten die Punkte von P . Die internen Knoten v von T speichern Vergleichswerte xv , um die Suche zu leiten: im linken Teilbaum sind genau die Punkte p mit p ≤ xv . Algorithmus 6.1 Eingabe: ein Baum T und zwei Zahlen x, x′ mit x ≤ x′ Ausgabe: der Knoten v, an dem sich die Pfade zu x und x′ trennen oder das Blatt, mit dem beide Pfade enden 1: Funktion FindeTrennKnoten(T , x, x′ ) 2: v ← Wurzel(T ) 3: Solange v kein Blatt von T ist und (x′ ≤ xv oder x > xv ), mache 4: Wenn x′ ≤ xv dann 5: v ← linkerSohn(v) 6: sonst 7: v ← rechterSohn(v) Eingabe: ein binärer Suchbaum T und ein Bereich [x, x′ ] Ausgabe: alle Punkte, die in P gespeichert sind und im Bereich [x, x′ ] liegen 8: Funktion 1DBereichsAbfrage(T , [x, x′ ]) 9: vt ←FindeTrennKnoten(T , x, x′ ) 10: Wenn vt ein Blatt ist, dann 11: teste, ob der Punkt in vt in [x, x′ ] liegt und liefere diesen gegebenenfalls zurück. 12: sonst 13: ⊲ Wir folgen dem Pfad zu x und liefern alle Punkte in Teilbäumen rechts des Pfades. 14: v ← linkerSohn(vt ) 15: Solange v kein Blatt ist, mache 16: Wenn x ≤ xv dann 17: LiefereTeilbaum(rechterSohn(v)) 18: v ← linkerSohn(v) 19: sonst 20: v ← rechterSohn(v) . 21: Liefere gegebenenfalls den Punkt, der in v gespeichert ist. 22: Folge analog dem Pfad zu x′ und liefere dabei alle Punkte in Teilbäumen links des Pfades. Behandel das gefundene Blatt. Lemma 6.2 Die Funktion 1DBereichsAbfrage(T , [x, x′ ]) von Algorithm 6.1 liefert genau die gesuchten Punkte. Satz 6.3 Sei P eine Menge von n Punkten im 1-dimensionalen Raum. Dann kann P in einem balancierten binären Suchbaum in Zeit in O(n log n) gespeichert werden, was Speicherplatz in O(n) benötigt. Damit können Punkte im Abfragebereich innerhalb einer Zeit in O(log n + k) gemeldet werden, wobei k die Anzahl der gelieferten Punkte ist. Algorithmische Geometrie, WS 2009/10 28 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE 6.2 6.2.1 Kd-Baum Struktur Es sei P nun eine Menge von n Punkten der Ebene. Wir nehmen an, dass keine zwei Punkte die selben x-Koordinaten haben und auch keine zwei Punkte die selben y-Koordinaten haben. Wir suchen alle Punkte von P im Rechteck R := [x, x′ ] × [y, y ′ ]. Verallgemeinerung des 1D-Prinzips zum Bauen der Suchstruktur: immer den Bereich in zwei gleich große Teile aufteilen, dort rekursiv Teilbäume bauen und dies zum Gesamtbaum zusammensetzen. Wegen zwei Koordinatenrichtungen wechseln wir einfach von Schritt zu Schritt zwischen horizontalen und vertikalen Trenngeraden. Diese gehen jeweils durch einen Punkt von P . 6.2.2 Aufbau Algorithmus 6.4 Eingabe: eine Punktmenge P ⊂ R2 und eine aktuelle Tiefe t Ausgabe: die Wurzel eines Kd-Baums, der P speichert 1: Funktion BaueKdBaum(P, t) 2: Wenn P nur einen Punkt enthält, dann 3: liefere ein Blatt, das genau diesen Punkt speichert. 4: sonst 5: Wenn t gerade ist, dann 6: teile P durch eine vertikale Gerade l in zwei Teilmengen. Dabei soll l durch den Punkt von P gehen, dessen x-Koordinate der kleinste Median der x-Koordinaten von P ist. P1 ist die Menge der Punkte in P , die links von l oder auf l liegen, und P2 ← P \ P1 . 7: sonst 8: Teile P durch eine horizontale Gerade l in zwei Teilmengen. Dabei soll l durch den Punkt von P gehen, dessen y-Koordinate der kleinste Median der yKoordinaten von P ist. P1 ist die Menge der Punkte in P , die unterhalb von l oder auf l liegen, und P2 ← P \ P1 . 9: vl ← BaueKdBaum(P1 , t + 1) 10: vr ← BaueKdBaum(P2 , t + 1) 11: Erzeuge einen neuen Knoten v mit linkem Sohn vl und rechten Sohn vr , in dem l gespeichert ist. 12: Liefere v zurück. Lemma 6.5 Ein Kd-Baum für eine Menge von n Punkten der Ebene benötigt Speicherplatz in O(n) und kann in Zeit in O(n log n) konstruiert werden. Beweis Der Median kann in linearer Zeit gefunden werden, jedoch nur mit einem kompliziertem Algorithmus. Wir umgehen dies durch Vorsortierung der Punkte in zwei Listen nach x- beziehungsweise nach y-Koordinate. Der Aufbau dieser Listen brauchen einmalig Zeit in O(n log n). Während der weiteren Bearbeitung können die Listen in linearer Algorithmische Geometrie, WS 2009/10 29 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE Zeit aktuell gehalten werden und liefern in konstanter Zeit den Median. Wir erhalten die folgende Rekursion für die oberer Laufzeitschranke T (n) ( O(1), falls n = 1 T (n) = O(n) + 2T (n/2), falls n > 1 , und damit T (n) ∈ O(n log n). 6.2.3 Abfrage mit dem Kd-Baum Zu jedem Knoten v gehört ein rechteckiger Bereich b(v) der Ebene. Dieser kann teilweise unbeschränkt sein, ist aber sonst nach rechts und oben abgeschlossen, nach links und unten dagegen offen. Wir steigen rekursiv in alle Teilbäume ab, deren Bereich den Abfragebereich R schneidet. Liegt b(v) ganz in R, so können wir diesen Teilbaum mittels LiefereTeilbaum(v) direkt ausgeben. Algorithmus 6.6 Eingabe: die Wurzel v eines (Teilbaumes von einem) Kd-Baum, und ein Bereich R Ausgabe: alle Punkte in Blättern des Kd-Baums, die in R ∩ b(v) liegen 1: Funktion SucheImKdBaum(v, R) 2: Wenn v ein Blatt ist, dann 3: liefere den Punkt in v, falls er zu R gehört. 4: sonst 5: Wenn b(linkerSohn(v)) ⊂ R, dann 6: LiefereTeilbaum(linkerSohn(v)) 7: sonst 8: Wenn b(linkerSohn(v)) ∩ R 6= ∅, dann 9: SucheImKdBaum(linkerSohn(v), R) 10: Wenn b(rechterSohn(v)) ⊂ R, dann 11: LiefereTeilbaum(rechterSohn(v)) 12: sonst 13: Wenn b(rechterSohn(v)) ∩ R 6= ∅, dann 14: SucheImKdBaum(rechterSohn(v), R) Während der Rekursion kann b(v) mit bestimmt werden. Algorithmus 6.6 ist unabhängig von der geometrischen Form von R gültig, R muss dazu kein Rechteck sein. Lemma 6.7 Eine Abfrage mit einem √ achsenparallelen Rechteck in einem Kd-Baum, der n Punkte speichert, kann in Zeit in O( n + k) ausgeführt werden, wobei k die Anzahl der gelieferten Punkte bezeichnet. Beweis LiefereTeilbaum(·) braucht lineare Zeit in der Anzahl der dabei gelieferten Punkte. Folglich brauchen alle Ausführungen von Zeile 6 und 11 zusammen Zeit in O(k). Die restliche Zeit läßt sich linear durch die Anzahl besuchter Knoten v abschätzen, die nicht an LiefereTeilbaum(·) übergeben wurden. Für diese Knoten ist b(v) ∩ R 6= ∅ aber b(v) 6⊂ R. Damit muß b(v) den Rand von R schneiden. Wir verlängern gedanklich die Randkanten von R und schätzen die Anzahl der Schnittpunkte von b(v) (für alle v) mit den 4 Geraden, die R begrenzen, ab. Wir betrachten als Beispiel eine vertikale Algorithmische Geometrie, WS 2009/10 30 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE Gerade l. Es sei T ein Kd-Baum mit Wurzel v. Dann kann l nur einen der beiden Bereiche b(linkerSohn(v)) oder b(rechterSohn(v)) schneiden, und entsprechend von den vier Enkeln b(linkerSohn(linkerSohn(v))) etc. nur zwei. Dies ergibt die folgende Rekursion für die maximale Anzahl Q(n) der von l geschnittenen Bereiche: ( O(1), falls n = 1 Q(n) = 2 + 2Q(n/4), falls n > 1 . √ √ Dies löst sich zu Q(n) ∈ O( n) auf. Damit ist 4Q(n) ∈ O( n) eine Schranke für die Anzahl der besuchten Knoten v, die nicht an LiefereTeilbaum(·) übergeben wurden. Satz 6.8 Ein Kd-Baum für eine Menge von n Punkten der Ebene benötigt Speicherplatz in O(n) und zum initialisieren Zeit in O(n log n). Eine √ Abfrage mit einem achsenparallelem Rechteck auf dem Kd-Baum benötigt Zeit in O( n+k), wobei k die Anzahl gelieferter Punkte ist. Für Kd-Bäume im Rd , d ≥ 2 gilt ein entsprechender Satz mit folgenden Abschätzungen: Speicherplatz in O(n), Initialisierungszeit in O(n log n), Abfragezeit in O(n1−1/d + k). 6.3 Range-Baum √ Wir wollen schneller abfragen können als mit der garantierten Zeit O( n + k) des kdBaumes. Der überlagerte Range-Baum erlaubt dies in Zeit in O(log n + k). Dafür steigt der Speicherplatzbedarf von O(n) auf O(n log n). Zunächst betrachten wir den (einfachen) Range-Baum mit einer Abfragezeit in O((log n)2 + k). Wir suchen in einer ersten Phase nur mit Hilfe der x-Koordinate. Für die zweite Phase nutzen wir dann angepasste lokale Informationen, um auch die verbliebene 1D-Abfrage für die zugehörige y-Koordinate effizient lösen zu können. Algorithmus 6.9 Eingabe: eine Punktmenge P der Ebene Ausgabe: die Wurzel eines 2-dimensionalen Range-Baums für P 1: Funktion Baue2DRangeBaum(P ) 2: Initialisiere die zugeordnete Struktur: baue einen binären Suchbaum Ts auf der Menge Py der y-Koordinaten. Speichere in den Blättern von Ts vollständig die Originalpunkte. 3: Wenn P nur einen Punkt enthält, dann 4: erzeuge ein Blatt v, das genau diesen Punkt und Ts speichert. 5: sonst 6: Teile P in zwei Teilmengen auf. Dazu sei xm der kleinste Median aller xKoordinaten in P . P1 sei die Menge der Punkte (x, y) in P mit x ≤ xm , und P2 ← P \ P1 . 7: vl ← Baue2DRangeBaum(P1 ) 8: vr ← Baue2DRangeBaum(P2 ) 9: Erzeuge einen neuen Knoten v mit Vergleichswert xv := xm , linkem Sohn vl , rechten Sohn vr , und zugeordneter Struktur Ts . 10: Liefere v zurück. Algorithmische Geometrie, WS 2009/10 31 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE Lemma 6.10 Ein Range-Baum für n Punkte der Ebene benötigt Speicherplatz in O(n log n). Beweis Ohne die zugeordneten Strukturen Ts benötigt der primäre Suchbaum linearen Speicherplatz in n. Jeder Punkt p ∈ P ist in jeder Tiefe des primären Suchbaums in genau einer zugeordneten Struktur gespeichert. Die maximale Tiefe ist in O(log n). Da der Speicherplatz jeder zugeordneten Struktur linear von der Anzahl der gespeicherten Punkte abhängt, ergibt sich die Behauptung. Algorithmus 6.11 Eingabe: ein 2-dimensionaler Range-Baum T und ein Bereich [x, x′ ] × [y, y ′ ] Ausgabe: alle Punkte, die in P gespeichert sind und im Bereich [x, x′ ] × [y, y ′ ] liegen 1: Funktion 2DBereichsAbfrage(T , [x, x′ ] × [y, y ′ ]) 2: vt ←FindeTrennKnoten(T , x, x′ ) 3: Wenn vt ein Blatt ist, dann 4: teste, ob der Punkt in vt in [x, x′ ] × [y, y ′ ] liegt und liefere diesen gegebenenfalls zurück. 5: sonst 6: ⊲ Wir folgen dem Pfad zu x und führen 1DBereichsAbfrage(Ts , [y, y ′ ]) für alle Teilbäume rechts des Pfades aus. 7: v ← linkerSohn(vt ) 8: Solange v kein Blatt ist, mache 9: Wenn x ≤ xv dann 10: 1DBereichsAbfrage(Ts (rechterSohn(v)), [y, y ′ ]) 11: v ← linkerSohn(v) 12: sonst 13: v ← rechterSohn(v) . 14: Liefere gegebenenfalls den Punkt, der in v gespeichert ist. 15: Folge analog dem Pfad von vt zu x′ und führe 1DBereichsAbfrage(Ts , [y, y ′ ]) für alle Teilbäume links des Pfades aus. Lemma 6.12 Eine Abfrage mit einem achsenparallelem Rechteck in einem Range-Baum mit n Punkten der Ebene benötigt Zeit in O((log n)2 + k), wobei k die Anzahl der gefundenen Punkte ist. Beweis Es sei v ein besuchter Knoten von T (ohne Ts ). Ausserhalb von 1DBereichsAbfrage benötigen wir nur konstante Zeit zur Behandlung von v. 1DBereichsAbfrage benötigt dagegen Zeit in O(log n′ + kv ), wenn Ts genau n′ ≤ Pn Punkte enthält, wovon kv als Antwort geliefert werden. Die Gesamtzeit ist damit in v O(log n + kv ), wobei die P Anzahl der Summanden in O(log n) und v kv = k ist. Satz 6.13 Es sei P eine Menge von n Punkten der Ebene. Ein Range-Baum für P kann mit Speicherplatz in O(n log n) in einer Zeit in O(n log n) aufgebaut werden. Durch eine Abfrage mit diesem Baum können die Punkte von P innerhalb eines achsenparallelen rechteckigen Abfragegebietes in Zeit in O((log n)2 +k) bestimmt werden, wobei k die Anzahl der gelieferten Punkte ist. Durch fractional cascading“ können wir die Zeit auf O(log n + k) verringern, ohne asym” ptotisch mehr Speicherplatz zu benutzen. Algorithmische Geometrie, WS 2009/10 32 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE Satz 6.14 Es sei P eine Menge von n Punkten im Rd mit d ≥ 2. Ein Range-Baum für P kann mit Speicherplatz in O(n(log n)d−1 ) in einer Zeit in O(n(log n)d−1 ) aufgebaut werden. Durch eine Abfrage mit diesem Baum können die Punkte von P innerhalb eines achsenparallelen rechteckigen Abfragegebietes in Zeit in O((log n)d + k) bestimmt werden, wobei k die Anzahl der gelieferten Punkte ist. Beweis Wir nutzen die selben Ideen wie im 2-dimensionalen Fall mittels Induktion. Der primäre Suchbaum kann in einer Zeit in O(n log n) aufgebaut werden. Die zugeordneten Strukturen sind (d − 1)-dimensionale Range-Bäume. Jeder Punkt aus P ist in jeder Tiefe des primären Baumes in genau einem zugeordneten Baum vertreten. Daher wird für eine feste Tiefe maximal Speicherplatz und Initialisierungszeit in O(n(log n)d−1 ) benötigt. Die maximale Tiefe ist in O(log n). Die Analyse der Abfrage funktioniert analog zu Lemma 6.12. 6.4 Entartete Lage Bisher hatten wir angenommen, dass keine zwei Punkte aus P eine Koordinate gemeinsam hatten. Dies wollen wir nun verallgemeinern. Der Range-Baum mit seinen Operationen kann auch für andere Zahlbereiche neben R genutzt werden. Diese Zahlbereiche müssen dabei mittels der <-Relation vollständig geordnet sein, unter anderem gilt für zwei Zahlen x und y immer genau eine der drei Beziehungen x < y, y < x oder x = y. Insbesondere gilt dies auch für Folgen von reellen Zahlen und sogar für Folgen in R ∪ {+∞, −∞}, wenn wir diese lexikographisch sortieren. Diese Folgen nennen wir zusammengesetzte Zahlen, sie bilden die Menge R̂. Wir transformieren die Punkte p = (x, y) von R2 nach (R̂)2 mittels p̂ := ((x, y), (y, x)). Was wird dabei aus einem Rechteck R = [x, x′ ] × [y, y ′ ] in R2 ? Wir wählen folgendes Rechteck in (R̂)2 : R̂ := [(x, −∞), (x′ , +∞)] × [(y, −∞), (y ′ , +∞)] Lemma 6.15 Sei p ein Punkt der Ebene und R ein rechteckiger Bereich. Dann ist p ∈ R ⇐⇒ p̂ ∈ R̂. Diese Transformation kann durch angepasste Vergleichsfunktionen implementiert werden, ohne dass die Paare in R̂ explizit gebildet werden müssen. Ersetz man (gedanklich alle Punkte und Rechtecke, sowie) tatsächlich alle Vergleiche entsprechend dieser Transformation, so erhält man Algorithmen, die nicht mehr die allgemeine Lage ihrer Eingabedaten voraussetzen. Dabei bleiben alle Abschätzungen gültig. Dieser Ansatz funktioniert analog in höheren Dimensionen. Algorithmische Geometrie, WS 2009/10 33 TUM, M9, Dr. Nico Düvelmeyer 6 ABFRAGEN ORTHOGONALER BEREICHE 6.5 Fractional Cascading Überlagerte Range-Bäume (layered range tree) verbinden die vielen zugeordneten 1dimensionalen Suchstrukturen eines 2-dimensionalen Range-Baumes, damit die wiederholten Abfragen mit selbem Suchbereich in verschiedenen, aber benachbarten Suchbäumen schneller durchgeführt werden können. Wir verändern dafür die 1-dimensionale Suchstruktur, die Knoten v zugeordnet ist, zu einem sortierten Feld. Neben den bisherigen Informationen zu Punkt p (z.B. dessen Koordinaten) enthalten die sortierten Felder auch noch zwei Verweise zu entsprechenden Positionen des Punktes p in den Listen von linkerSohn(v) und rechterSohn(v). Als entsprechende Position verstehen wir dabei die Position von p oder, falls p nicht in der anderen Liste enthalten ist, eines Punktes mit nächstgrößerem Such-Schlüssel (dies ist die letzte Koordinate). Folgt man dem Verweis, so findet man also den kleinsten Punkt der anderen Liste, der größer oder gleich p ist. Gibt es so einen Punkt nicht, so speichern wir den symbolischen Wert nil. Satz 6.16 Sei P eine Menge von n Punkten im Rd , d ≥ 2. Ein überlagerter Range-Baum für P benötigt Speicherplatz in O(n(log n)d−1 ) und kann in Zeit in O(n(log n)d−1 ) aufgebaut werden. Durch eine Abfrage mit diesem Baum können die Punkte von P innerhalb eines achsenparallelen rechteckigen Abfragegebietes in Zeit in O((log n)d−1 + k) bestimmt werden, wobei k die Anzahl der gelieferten Punkte ist. Beweis Im 2-dimensionalen Fall suchen wird die Anfangsposition des Abfragegebiets [x, x′ ] × [y, y ′ ], genauer den ersten Punkt (px , py ) mit py ≥ y, in der Suchstruktur Ts (vt ) des Trennknotens vt . Dies braucht Zeit in O(log n). Auf der Suche ausgehend von vt nach den Werten x und nach x′ halten wir diese Position in jeweils konstanter Zeit aktuell. Eine Anpassung von LiefereTeilbaum(v) läuft damit in Zeit in O(1 + kv ). Die weitere Analyse folgt wie zuvor ohne fractional cascading. Algorithmische Geometrie, WS 2009/10 34 TUM, M9, Dr. Nico Düvelmeyer