schreiben an .

Werbung
RANGE TREES
MBE, APRIL 2008
Zusammenfassung. Es wird eine Datenstruktur beschrieben (range tree), welche Bereichsabfragen für d-dimensionale Punktemengen in O(logd n + k) Zeit ermöglicht, wobei k die Anzahl ausgegebener Punkte sind. Die Datenstruktur kann in O(n logd−1 n)
Zeit konstruiert werden und benötigt O(n logd−1 n) Speicher. Die Abfragezeit kann auf
O(logd−1 n + k) verbessert werden, durch verwenden von fractional cascading.
Das meiste hier ist sehr stark beeinflusst von [1] ab Seite 105, teilweise sogar 1:1. Da das
Buch jedoch nicht allen leicht zugänglich ist, und zudem auf Englisch, ist dieses Dokument
wohl nicht völlig sinnlos :-). Bei Unklarheiten, Verbesserungen etc. ungeniert ein E-Mail
schreiben an [email protected].
1. Motivation
Seien n Punkte {p 1 , p 2 , . . . , p n } im dreidimensionalen Raum R3 gegeben durch x-, y- und
z-Koordinate. (Für was ein solcher Punkt im realen Leben genau steht, soll uns im weiteren
nicht interessieren, aber der Leser darf und soll gerne seiner Fantasie freien Lauf lassen.)
Unser Ziel ist es Bereichsabfragen möglichst effizient durchzuführen. D.h. wir würden beispielsweise alle Punkte p i = (x i , y i , z i ) bestimmen wollen, wo
1.41
−53.58
23.84
≤
≤
≤
xi
yi
zi
≤ 59.26
≤ 97.93
≤ 62.643
62.643
gilt. Geometrisch betrachtet wären das alle Punkte innerhalb des Quaders
23.84
[1.41, 59.26] × [−53.58, 97.93] × [23.84, 62.643].
59.26
Ein erster leicht implementierbarer Ansatz wäre sicherlich für jeden der n Punkte zu testen, ob dieser im vorgegebenen Bereich enthalten ist, und falls ja, diesen zum Resultat
hinzuzufügen. Somit hätten wir eine Laufzeit von O(n). Man kann sich leicht vergewissern, dass dies auch worst-case-optimal ist, denn falls unser Anfragebereich gross genug
ist, werden alle n Punkte im Resultat enthalten sein. Und um n Punkte auszugeben benötigen wir linear viel Zeit.
Ok.. und jetzt, fertig? Es kann ja scheinbar nichts besseres geben??
Die Datenstruktur, die wir jetzt ansehen werden, ermöglicht einen Abfragealgorithmus,
der im worst-case auch lineare Laufzeit hat. Im Unterschied zum vorherigen Ansatz ist
dieser jedoch output-sensitive. D.h. die Laufzeit ist nicht nur abhängig von der Grösse des
Inputs (n), sondern auch von der Grösse des Resultats (k). Falls hier also k ≪ n, dann wird
— wie wir im Folgenden sehen werden — unsere Laufzeit sublinear.
1.41
-53.58
97.93
2. Range Trees (2-Dimensional)
Wir werden uns zu Beginn nicht auf den allgemeinen d-dimensionalen Fall stürzen, sondern die Funktionsweise anhand des d = 2 Falles vergegenwärtigen. (Danach ist die Verallgemeinerung nicht mehr schwierig.) Gegeben sei also eine Menge P von n Punkten in
der Ebene, wo wir 2-dimensionale Bereichs-Suchabfragen ausführen wollen. Der range tree
(wortwörtlich übersetzt “Bereichsbaum”) ist nun folgendermassen aufgebaut:
● Die Basis besteht aus einem balancierten binären Blattsuchbaum1 T , gebildet aus
den x-Koordinaten der Punkte aus P.
● Jeder (innere oder äussere) Knoten v in T hat einen Zeiger auf einen anderen Blattsuchbaum Tassoc (v). Dieser besteht jeweils aus Kopien aller Blätter2 des Teilbaumes
mit Wurzel v in T , diesmal jedoch aufgebaut nach y-Koordinaten.
1Ein Blattsuchbaum entspricht einem normalen (binären) Suchbaum, mit dem Unterschied, dass die Schlüssel
in den Blättern gespeichert werden. In den inneren Knoten sind nur “Wegweiser” vorhanden. Beispielsweise
könnte ein innerer Knoten den maximalen Schlüsselwert seines linken Teilbaums speichern. (Alles was grösser
ist, wird somit im rechten Teilbaum sein). Wie auch immer, endet die Suche somit immer in einem Blatt, was hier
essentiell ist! (siehe auch Abbildung rechts)
2Diese Blätter mit Wurzel v werden im weiteren als P(v) gekennzeichnet.
1
49
23
80
10
3
3
19
30
89
62
37
49
10 19 23 30 37
μ
Blattsuchbaum
59
70
89
59 62 70 80
μ
100
100 105
Abbildung 1. Aufbau der range trees
Wir nehmen an, dass keine der Punkte aus P = {p 1 , . . . , p n } dieselbe x- oder y- Koordinate
haben3. Nun kann man den range tree folgendermassen rekursiv aufbauen, falls man als
Input die Punkte P nach x-Koordinate sortiert erhält:
Algorithm B UILD 2DR ANGE T REE(P)
Input. A set P of points in the plane.
Output. The root of a 2-dimensional range tree.
1. Construct the associated structure: Build a binary search tree Tassoc on the set Py of ycoordinates of the points in P. Store at the leaves of Tassoc not just the y-coordinate of the
points in Py , but the points themselves.
2. if P contains only one point
3.
then Create a leaf ν storing this point, and make Tassoc the associated structure of ν.
4.
else Split P into two subsets; one subset Pleft contains the points with x-coordinate less
than or equal to xmid , the median x-coordinate, and the other subset Pright contains
the points with x-coordinate larger than xmid .
5.
νleft ← B UILD 2DR ANGE T REE(Pleft )
6.
νright ← B UILD 2DR ANGE T REE(Pright )
7.
Create a node ν storing xmid , make νleft the left child of ν, make νright the right
child of ν, and make Tassoc the associated structure of ν.
8. return ν
p
p
p
p
Lemma. Ein 2-dimensionaler range tree benötigt O(n log n) Speicher.
Beweis. Jedes p i ∈ P wird für eine gegebene Tiefe von T in genau einem referenzierten
Tassoc (v) vorhanden sein. Da die Höhe logarithmisch beschränkt ist und der Basisbaum
(mit den x-Koordinaten) nur linearen Speicher benötigt, folgt die behauptete Aussage daraus.
So wie Build2dRangeTree geschrieben ist, wird die Konstruktionszeit nicht O(n log n)
betragen. Dies liegt am Schritt 1, welcher schon für sich selber anfänglich O(n log n) Zeit
kostet. Um die totale Zeit auf die geforderte Schranke hinunterzubringen, muss man die
Punkte (in einer separaten Liste) auch schon nach der y-Koordinate sortiert haben, bevor man den Algorithmus aufruft. Danach kann der Schritt 1 in linearer Zeit ausgeführt
werden. (Wie?)
Die Datenstruktur ist nun aufgebaut und was folgt ist der eigentliche Abfragealgorithmus.
Wir versuchen nun kurz eine Intuition zu bekommen, wie der Ablauf ist, und sehen uns
danach den Pseudocode an.
Nehmen wir an unser Abfragebereich wäre [x ∶ x ′ ] × [y ∶ y ′ ]. Im eindimensionalen Fall
(vergessen wir einmal y) werden wir alle Knoten suchen, welche im gegeben x-Intervall
liegen. Hierzu suchen wir von der Wurzel aus startend nach x und x ′ im Baum, bis sich
die beiden Suchpfade an einem Knoten vsplit trennen. Von vsplit ’s linkem Kind aus suchen
wir nach x. Dabei geben wir jedes Mal, wenn wir an einem Knoten v links abbiegen, alle
Punkte des rechten Teilbaumes von v aus. Analog suchen wir von vsplit ’s rechtem Kind aus
nach x ′ und geben jedes Mal, wenn wir an einem Knoten v rechts abbiegen, alle Punkte des
linken Teilbaums von v aus. Schlussendlich überprüfen wir noch die beiden Blätter µ und
µ′ , wo die beiden Pfade endeten, um festzustellen ob sie im gewünschten Bereich liegen.
3Diese Annahme kann implizit entfernt werden. Wie?
26
2
root(T)
νsplit
μ
die ausgewählten Teilbäume
μ
F IND S PLIT N ODE(T, x, x )
Input. A tree T and two values x and x with x x .
Output. The node ν where the paths to x and x split, or the leaf where both paths end.
1. ν ← root(T)
2. while ν is not a leaf and (x xν or x > xν )
3.
do if x xν
4.
then ν ← lc(ν)
5.
else ν ← rc(ν)
6. return ν
Algorithm 1DR ANGE Q UERY(T, [x : x ])
Input. A binary search tree T and a range [x : x ].
Output. All points stored in T that lie in the range.
1. νsplit ←F IND S PLIT N ODE(T, x, x )
2. if νsplit is a leaf
3.
then Check if the point stored at νsplit must be reported.
4.
else (∗ Follow the path to x and report the points in subtrees right of the path. ∗)
5.
ν ← lc(νsplit )
6.
while ν is not a leaf
7.
do if x xν
8.
then R EPORT S UBTREE(rc(ν))
9.
ν ← lc(ν)
10.
else ν ← rc(ν)
11.
Check if the point stored at the leaf ν must be reported.
12.
Similarly, follow the path to x , report the points in subtrees left of the path, and
check if the point stored at the leaf where the path ends must be reported.
Wie kommen jetzt die y-Koordinaten ins Spiel? — Erhöhen wir die Dimension des eben
beschriebenen Algorithmuses. Statt jeweils alle Punkte des rechten bzw. linken Teilbaumes
eines Knoten v auszugeben (Linie 8), rufen wir neu stattdessen die Prozedur 1DRangeQuery auf mit dem gesuchten y-Bereich! Wir erhalten somit folgenden (sehr leicht modifizierten) Algorithmus:
Algorithm 2DR ANGE Q UERY(T, [x : x ] × [y : y ])
Input. A 2-dimensional range tree T and a range [x : x ] × [y : y ].
Output. All points in T that lie in the range.
1. νsplit ←F IND S PLIT N ODE(T, x, x )
2. if νsplit is a leaf
3.
then Check if the point stored at νsplit must be reported.
4.
else (∗ Follow the path to x and call 1DR ANGE Q UERY on the subtrees right of the
path. ∗)
5.
ν ← lc(νsplit )
6.
while ν is not a leaf
7.
do if x xν
8.
then 1DR ANGE Q UERY(Tassoc (rc(ν)), [y : y ])
9.
ν ← lc(ν)
22
10.
else ν ← rc(ν)
11.
Check if the point stored at ν must be reported.
12.
Similarly, follow the path from rc(νsplit ) to x , call 1DR ANGE Q UERY with the
range [y : y ] on the associated structures of subtrees left of the path, and check if
the point stored at the leaf where the path ends must be reported.
Wie würde nun die Prozedur 3DRangeQuery aussehen? Wie 4DRangeQuery? dDRangeQuery?
Lemma. Eine Bereichsabfrage eines 2-dimensionalen range trees mit n Punkten benötigt
O(log2 n + k) Zeit, wobei k die Anzahl ausgegebener
Punkte ist.
23
Beweis. Is left to the reader as an exercise :-)
Fazit für 2-dimensionale range trees:
Theorem. Sei P eine Menge von n Punkten in der Ebene. Ein range tree für P...
● benötigt O(n log n) Speicher,
● kann in O(n log n) konstruiert werden,
● und ermöglicht Bereichsabfragen in O(log2 n + k) Zeit,
wobei k the Anzahl ausgegebener Punkte ist.
N.B. durch benutzen von fractional cascading kann die Abfragezeit auf O(log n+k) verbessert werden. Sehr elegante Technik, wird aber an dieser Stelle übersprungen. (Siehe hierzu
[1])
3
3. Range Trees (d-Dimensional)
Wie wir gesehen haben, ist die Idee zur Verallgemeinerung nun einigermassen klar (falls
nicht, nochmals durchdenken!). Wir verzichten deshalb auf einen genaueren Beschrieb an
dieser Stelle und geben die Resultate für den allgemeinen Fall an:
Theorem. Sei P eine Menge von Punkten im d-dimensionalen Raum, wobei d ≥ 2. Ein range
tree für P...
● benötigt O(n logd−1 n) Speicher,
● kann in O(n logd−1 n) konstruiert werden,
● und ermöglicht Bereichsabfragen in O(logd n + k) Zeit,
wobei k the Anzahl ausgegebener Punkte ist.
N.B. durch benutzen von fractional cascading kann hier wiederum die Abfragezeit auf
O(logd−1 n + k) verbessert werden.
Literatur
[1] Mark de Berg, Otfried Cheong, Marc van Kreveld, and Mark Overmars. Computational Geometry: Algorithms
and Applications. Springer-Verlag, third edition, March 2008.
4
Herunterladen