Rheinische Friedrich-Wilhelms-Universität Bonn Institut für Informatik I Rainer Herter Volumenbestimmung vereinigter Würfel 17. Mai 2008 Seminararbeit im Sommersemester 2008 Zusammenfassung In dieser Ausarbeitung wird ein Algorithmus vorgestellt, welcher das Volumen von sich (nicht zwangsweise) schneidenden, achsenparallelen Würfeln im R3 berechnet. Dabei greift der Algorithmus auf das Verfahren des Divide & Conquer zurück und erreicht damit eine Laufzeit von O(n4/3 · log n), wobei n die Anzahl der Würfel ist. Inhaltsverzeichnis 1 Einführung 2 2 Der Algorithmus 2.1 Grundlagen und allgemeine Vorgehensweise 2.2 Flächenberechnung innerhalb einer cell . . . 2.2.1 Die Berechnung von ψ . . . . . . . . 2.2.2 Die Berechnung von ϕ . . . . . . . . 2.3 Die Bäume T (P), T (C̃) und T (R) . . . . . . 2.3.1 Übersicht . . . . . . . . . . . . . . . 2.3.2 Verarbeitung von T (P) . . . . . . . . 2.3.3 Verarbeitung von T (C̃) . . . . . . . 2.3.4 Verarbeitung von T (R) . . . . . . . 3 Zusammenfassung . . . . . . . . . 2 . . . . . . . . . 2 . . . . . . . . . 7 . . . . . . . . . 12 . . . . . . . . . 12 . . . . . . . . . 13 . . . . . . . . . 13 . . . . . . . . . 14 . . . . . . . . . 15 . . . . . . . . . 18 21 1 1 Einführung In dieser Ausarbeitung wird ein Algorithmus von Agarwal, Kaplan und Sharir vorgestellt, der das Volumen sich schneidender Würfel im R3 in Zeit O(n4/3 · log n) berechnen kann. [1] Dabei wird (unter anderem) die allgemeine Lage der Würfel angenommen, und dass sich die Würfel in ihrer Größe unterscheiden dürfen. Die Problematik dieser Berechnung ist auf Victor Klee mit seinem Klee’s ” measure problem“ zurückzuführen, der die Vereinigung von n Intervallen im R1 in Zeit O(n · log n) durchführte und wissen wollte, ob sein Algorithmus damit optimal wäre (was mittlerweile bewiesen wurde). Für Würfel gleicher Größe existiert beispielsweise ein Algorithmus, der die Volumenberechnung schon in Zeit O(n · log n) durchführt. Diese Ausarbeitung ist in mehrere Unterkapitel unterteilt. Abschnitt 2.1 gibt eine gute Übersicht darüber, wie der Algorithmus im Allgemeinen arbeitet. Abschnitt 2.2 geht dann näher auf die Details der partiellen Flächenberechnung innerhalb einer cell ein, während Abschnitt 2.3 die Details bei der Verwaltung der von dem Algorithmus verwendeten Bäume eingeht. Eine Zusammenfassung der Laufzeitanalysen im Abschnitt 3 wird eine Gesamtlaufzeit von O(n4/3 ·log n) des vorgestellten Algorithmus’ bestätigen. Ein praktisches Anwendungsbeispiel wäre z.B. die Volumenberechnung von komplexen Räumen. 2 Der Algorithmus Für den präsentierten Algorithmus gehen wir davon aus, dass sich unsere n Würfel in allgemeiner Lage befinden. Für uns soll dies bedeuten, dass keine Ebene im R3 existiert, auf der 2 oder mehr Seiten unterschiedlicher Würfel liegen. Der Algorithmus bedient sich dem Prinzip von Divide & Conquer, indem es das Volumenproblem auf ein Problem in der Ebene reduziert, und die Flächenberechnung auf der Ebene durch Aufteilen in Teilprobleme löst. 2.1 Grundlagen und allgemeine Vorgehensweise Betrachten wir die z-Koordinaten der Würfel. Da allgemeine Lage gefordert ist, haben wir bei n gegebenen Würfeln exakt 2n unterschiedliche zKoordinaten, nämlich für jeden Würfel sowohl Ober- als auch Unterseite. In Zeit O(n · log n) können wir die Zahlen z1 bis zn aufsteigend sortieren. Die Zahlen speichern wir in der Menge Zc . Ausgehend von n achsenparallelen Würfeln im R3 , die wir in der Menge C speichern, benutzen wir eine Sweep-Ebene Π, die von z = −∞ hoch zu z = +∞ den Raum durchläuft und dabei bei jedem zi ein Sweep-Ereignis 2 eintritt, denn genau an den Stellen müssen Berechnungen durchgeführt und Datenstrukturen aktualisiert werden. Offensichtlich gilt für 1 ≤ i < 2n, dass für jedes z ! ∈ (zi , zi+1 ) sich der Schnitt U (C) ∩ Π(z ! ) nicht ändert. U (C) sei dabei das vom Algorithmus zu bestimmende Gesamtvolumen der Würfel, und Π(z ! ) die Sweep-Ebene an der z-Koordinate z ! . Definition 1 Sei ai die Fläche von U (C) ∩ Π(z ! ). Dann ist V olU (C) = 2n−1 ! i=1 ai · (zi+1 − zi ) das zu berechnende Gesamtvolumen. Das einzige, was der Algorithmus folglich noch erledigen muss, ist, die (2n − 1) Stück ai ’s effizient zu bestimmen. Der Schnitt U (C) ∩ Π(z ! ) ist die Vereinigung von einer Menge S an (2dimensionalen) Quadraten, die sich zu den Zeitpunkten z ! ∈ Zc ständig ändern kann (aber nicht zwangsweise muss). Ein Quadrat wird hinzugefügt, wenn unsere Sweep-Ebene auf die Unterseite, und entfernt, wenn unser Sweep-Ebene auf die Oberseite eines Würfels trifft. Sei AreaU (S) die Fläche der Vereinigung der Quadrate aus S. Betrachten wir im Folgenden die xy-Projektion des R3 . Wir sehen“ un” sere Würfel also quasi aus der Sicht unserer Sweep-Ebene und befinden uns nunmehr nur noch im R2 . Definition 2 Sei V ⊂ R2 diejenige Menge an Punkten unserer Würfel dieser Projektion. Dann ist |V | = 4n. Definition 3 Sei B die Boundingbox von V (das kleinste achsenparallele Rechteck, welches die Menge V beinhaltet, siehe Abbildung 1), und sei s definiert als s = 2 · n1/3 . Wir unterteilen unsere Boundingbox B nun in s (nicht zwangsweise gleichgroße) Rechtecke B1 bis Bs , welche ausgeglichen viele Punkte aus V 2/3 ) viele. Die Rechtecke B bis einschließen, dabei aber höchstens 4n 1 s = O(n Bs nennen wir im Weiteren slabs“. ” Nun wird jedes slab Bi in so genannte cells“ cij unterteilt: Pro slab ” Bi erzeugen wir s viele gleichverteilte Unterteilungen. Jede cell“ cij hat ” 1/3 ) viele Punkte aus V (Siehe Abbildung 2). folglich höchstens 4n = O(n 2 s Definition 4 Die belegte Fläche αi innerhalb eines slabs Bi , 1 ≤ i < s, ist gegeben als αi = AreaU (S) ∩ Bi . 3 BoundingBox B Y X Abbildung 1: BoundingBox B und Koordinatensystem zur Orientierung. slabs BoundingBox B cell Y X Abbildung 2: Unterteilung in slabs und cells. Damit wir die Gesamtfläche ai respektive die belegte Fläche αi in den einzelnen slabs Bi effizient berechnen können, benötigen wir eine geeignete Datenstruktur. Verwendet wird hier ein Binärbaum Ti , der für jeden slab Bi angelegt wird. Er wird uns beim Prinzip von Divide & Conquer unterstützen, um die belegten Flächen zu berechnen und zu verwalten. Der Aufbau der Bäume ist relativ simpel. Die Verwaltung jedoch leider nicht. Aufgebaut ist solch ein Baum folgendermaßen: Die Wurzel des Baumes speichert die Fläche αi wie oben definiert. Diese ändert sich zur Laufzeit be jedem z ∈ Zc . Jeder Knoten von Ti , die Wurzel mit eingeschlossen, ist mit einem Rechteck !v aus Bi assoziiert, welches aus 1 ≤ i ≤ s benachbarten cells cij bis cik besteht. 4 Die Blätter des Baumes sind folglich jeweils eine einzelne cell cil . Sie sind zusätzlich sortiert: Das linkeste Blatt v von Ti ist die unterste cell ci1 von Bi . Für einen Knoten v mit den Kindern w und z ist !v = !w ∪ !z . Definition 5 Sei v ein Knoten aus Ti . Die Teilmenge Sv ⊆ S ist diejenige Menge an Quadraten, dessen Ränder sich mit !v schneiden. Definition 6 Sei v ein Knoten aus Ti . Die Teilmenge Sv∗ ⊆ S sei die Menge an Quadraten, die zwar !v , jedoch nicht den Vaterknoten !u komplett enthalten. aus Sv∗ Flaeche von Vaterknoten aus Sv Y X Abbildung 3: Sv und Sv∗ für eine cell !v . Das oberste Quadrat ist nicht aus der Menge Sv∗ , da es auch !u komplett enthält. Insgesamt speichern wir in einem Knoten v die Werte αv , Sv und σv = |Sv∗ |. Allgemein gilt: αv = AreaU (Sv ∪ Sv∗ ) ∩ !v . cell Y aus Sv X Abbildung 4: αv einer cell !v und 3 Elementen aus Sv . 5 Hier unterscheiden wir bei den Knoten zwischen Blättern und inneren Knoten. Die Berechnung der Fläche für innere Knoten ist relativ einfach: " !v , falls σv ≥ 1 αv = (1) αw + αz , falls σv = 0 wobei w, z Kindknoten von v sind. Die Flächenberechnung des Knotens v, wenn dieser ein Blattknoten und damit eine cell ist, ist durchaus schwieriger und wird in Unterkapitel 2 erläutert. Mit unseren slabs, cells und den Bäumen Ti zu den slabs kommen wir nun zu unserer Ereignis-Struktur: Wenn wir ein Rechteck r einfügen wollen (wir also mit unserer SweepEbene auf eine Unterseite eines Würfels treffen“), suchen wir zuerst alle ” die slabs Bi , die von dem einzufügenden Rechteck r geschnitten werden. Es gibt 2 Fälle, die wir hier unterscheiden müssen: Zum einen kann eine cell cij den Rand des Rechtecks r enthalten, zum anderen kann sie auch komplett im Rechteck r enthalten sein. Fall 1: In diesem Falle betrachten wir cells, die den Rand des Rechtecks r enthalten. Weiter suchen wir uns für jedes der gefundenen Bi diejenigen cells cij , die mindestens ein Teilstück vom Rand von r enthalten. Die Suche beschränkt sich dabei in diesem Fall nur auf Blattknoten v des Baumes Ti , für die !v ∩ ∂r *= ∅. Für jeden Knoten v davon fügen wir dann r in die Menge Sv ein und aktualisieren αv entsprechend dem Verfahren, welches in Unterkapitel 2 vorgestellt wird. Fall 2: In diesem Falle wollen wir herausfinden, welche cells komplett im Rechteck r liegen. Für diejenigen cells ändert sich die Menge Sv∗ respektive der Wert σv = |Sv∗ |. Gefunden und aktualisiert werden können diese Blattknoten wie folgt: Eine cell cij kann nur komplett im Rechteck r liegen, wenn sich in dem zum cell dazugehörigen slab Bi kein Knoten von r befindet, Bi aber dennoch von r geschnitten wird. Um die gesuchten Bi zu finden, müssen wir lediglich die entsprechenden aus der zuvor gefundenen Teilmenge der Bi herausfiltern, die keinen Knotenpunkt von r in ihrem Bereich haben. Betrachten wir nun also diejenigen Bi ohne Knotenpunkt aus r. Dann existiert eine cell c1 , die einen Teil der unteren Kante, und eine cell c2 , die einen Teil der oberen Kante von r enthält. Wie bereits erwähnt, 6 entspricht jede cell in slab Bi einem Blattknoten aus Ti . Sei w der Knoten zu c1 und z der Knoten zu c2 aus Ti . Da die Knoten sortiert sind, suchen wir alle O(log n) Blattknoten u aus Ti , die zwischen den Knoten w und z liegen, und folgende Bedingung erfüllen: Sei p(u) der Vaterknoten von u. Dann: • !u ⊆ r • !p(u) *⊆ r Damit haben wir alle Blattknoten u und damit wiederum alle notwendigen cells aus Bi gefunden, die komplett im Rechteck r enthalten sind. Für jeden dieser Knoten u erhöhen wir aus gegebenem Anlass σu um 1 und setzen den im Knoten gespeicherten Wert der belegten Fläche αu = !u . Nachdem nun alle Blattknoten aus allen vom Rechteck r betroffenen Bäume Ti aktualisiert wurden, müssen wir nach der Idee des Divide & Conquer-Verfahrens bis zur Wurzel der Bäume alle Vorfahren der aktualisierten Knoten ebenfalls aktualisieren, um in der Wurzel den aktuellen Wert der belegten Fläche pro slab Bi erhalten. Dabei genügt es, den Wert αv entsprechend Formel 1 zu berechnen. Die in ganz B belegte Fläche ergibt sich trivialerweise durch die Summe der einzelnen Bi ’s: s ! αGesamt = αW urzel (Ti ) i=1 2.2 Flächenberechnung innerhalb einer cell Sehen wir uns nun genauer die Flächenberechnungen innerhalb einer cell an, die nicht komplett in dem einzufügenden Rechteck r liegt. Sei ! eine cell mit ! = [x0 , x1 ] × [y0 , y1 ]. O.B.d.A können wir annehmen, dass eine cell eine größere Breite als Höhe hat. Cells, für die dies umgekehrt ist, lassen sich analog behandeln. Seien weiterhin die Eckpunte von ! gegeben als p0 = (x0 , y0 ), p1 = (x1 , y0 ), p2 = (x1 , y1 ) und p3 = (x0 , y1 ) und sei S die Menge an Quadraten, dessen Ränder ! schneiden. Innerhalb von S unterscheiden wir zwischen 5 verschiedenen Quadraten, die vorkommen können: • Upper Rim: Eine Menge U an Quadraten, die die komplette obere Kante von ! enthalten. Wir speichern U in einer absteigend sortierten Liste nach y-Koordinaten. cl sei dabei die kleinste y-Koordinate der unteren Kanten aller Upper Rims. • Lower Rim: Eine Menge L an Quadraten, die die komplette untere Kante von ! enthalten. Wir speichern L in einer aufsteigend sortierten Liste nach y-Koordinaten. f l sei dabei die größte y-Koordinate der oberen Kanten aller Lower Rims. 7 • Pillar: Eine Menge P an Quadraten, die die obere und untere Kante von ! schneiden. • Corner: Eine Menge C an Quadraten, die genau einen Eckpunkt von boxempty enthalten. Von daher liegt für jeden dieser Quadrate genau ein Eckpunt des Quadrates in !. • Floater: Eine Menge F an Quadraten, die 2 oder 4 Punkte innerhalb von ! liegen haben. Corner Pillar p3 p2 Upper Rim ceiling floor p1 p0 Lower Rim Floater Abbildung 5: Die 5 Typen an schneidenden Quadraten, ceiling, floor und die Punkte p0 , p1 , p2 und p3 ,. Die ersten 3 Typen werden dabei als long“ bezeichnet (da sie keine ” Vertices innerhalb von ! haben), und die Corners und Floaters als short“, ” da diese mindestens einen Vertex in ! liegen haben. Es ist einfach, die belegende Fläche der einzelnen Typen innerhalb der cell zu berechnen, aber die gemeinsame ist etwas komplizierter. Daher benutzen wir die eben definierten Typen an Quadraten, um folgende Teilflächen zu definieren: 1. Zuerst betrachten wir die Fläche, die von Upper- und Lower Rims belegt wird (A1 ). 2. Dann die Fläche, die von den Pillars, aber nicht von den Rims belegt wird (A2 ). 3. Die Fläche, die von Corners, aber nicht von Pillars oder Rims belegt wird (ψ). 4. Und letztlich die Fläche, die von Floaters, aber nicht von Rims, Pillars oder Corners belegt wird (ϕ). 8 Mit diesen 4 Flächen haben wir die komplette in der betrachteten cell ! belegte Fläche α! gegeben. α! berechnet sich daher wie folgt: Area(!) (x − x )[(f l − y0 ) + (y1 − cl)] + (cl − f l) ·π +ψ +ϕ 1 0 αv = ' () * ' () * =: A1 =: A2 , falls cl ≤ f l , sonst (2) wobei π die akkumulierte Kantenlänge aller oberen Kanten der in ! vorkommenden Pillars ist (siehe Bild). A2 π ψ A1 ϕ Abbildung 6: Alle Flächenteile A1 , A2 , ψ und ϕ zur Berechnung von αv . Die Flächen A1 und A2 sind schnell berechnet, während die Bestimmung von ψ und ϕ aufwändiger ist. Bevor wir zu den Details der Flächenberechnung von ψ und ϕ kommen, müssen wir uns noch eine Definition anschauen: Definition 7 Ein rechtwinkliges Polygon heißt staircase“, wenn es eine ” rechtwinklige polygonale Kette gibt, die sowohl x- als auch y-monoton ist und die Eckpunte dieser Kette mit einer horizontalen Kante eh und einer vertikalen Kante ev verbunden werden. Den gemeinsamen Eckpunkt von eh und ev nennen wir apex“ (siehe ” Abbildung 7). Um die belegte Fläche ψ und ϕ in der cell ! zu berechnen, teilen wir die belegte Fläche zuerst in bis zu 4 dieser staircase“-Polygone P0 bis P3 ” anhand unserer Corner-Quadrate auf, wobei der apex jedes Polygons Pi der entsprechende Eckpunt pi von ! sein soll. Dabei kann es natürlich vorkommen, dass ein Polygon Pi einem Pj Fläche abschneidet, wenn sich Pi und Pj schneiden. Der Schnitt dieser Flächen wird dann entweder Pi oder Pj zugewiesen. 9 apex P3 apex P2 P2 P3 P1 P0 apex P0 apex P1 ”nibble off” Abbildung 7: Corners innerhalb einer cell und die daraus gebildeten staircase-Polygone. Markiert ist einer der beiden nibble off’s“. ” Nun unterteilen wir jedes der Polygone Pi in eine Menge C̃i an Rechtecken, indem wir von jedem Vertex von Pi innerhalb der cell eine vertikale Kante ziehen, bis wir auf die horizontale Kante eh von Pi treffen (siehe Abbildung 8). C̃3 C̃2 C̃0 C̃1 Abbildung 8: Unterteilung in die Mengen C̃i ’s. Die Menge C̃0 besteht beispielsweise aus 3 Rechtecken. + Sei C̃ = 3i=0 C̃i . Offensichtlich ist U (C̃) = U (C) ∩ !. Die Fläche ψ erhalten wir aus Area(C̃) und dem Schnitt mit den longTypen, also den Pillars aus P und den Rims aus U und L, wobei hierfür cl und f l ausreichen. Zur Bestimmung von ϕ können wir wiederum C verwenden. Speichern werden wir jedoch erstmal nur die Fläche der Floaters ohne die Corners, da wir später genau wie bei ψ den Schnitt mit den long-Typen einfach bestimmen können. Dazu berechnen wir U (F ∪ !) \ U (C) und teilen die Fläche ebenfalls in paarweise disjunkte vertikale Rechtecke, wie schon zuvor bei C̃. 10 Die Menge an daraus resultierenden Rechtecken speichern wir in der Menge R = {R1 , . . . , Ru }. Da U (F) O(|F|) und U (C) O(|C|) Vertices haben, hat die Menge R offensichtlich u = O(|F| + |C|) viele Elemente. Definition 8 Wir nennen ein Rechteck r ∈ R ein Stalagtit, wenn r die Kante ceiling schneidet, und ein Stalagmit, wenn r die Kante f loor schneidet. r kann Stalagtit und Stalagmit zugleich sein. Definition 9 Sei die Menge R gegeben. Dann bezeichne SM die Menge der Stalagmiten, und ST die Menge der Stalagtiten aus R. Stalagtiten cl fl Stalagmiten Abbildung 9: Links: cell mit staircase-Polygonen und Floaters. Rechts: Daraus gebildete Menge R. Nachdem die einzelnen Flächenteile A1 , A2 , ψ und ϕ berechnet wurden, ist die in der cell ! insgesamt belegte Fläche komplett berechnet worden. Doch wie ist nun die Laufzeit dieser Berechnung? Wir erinnern uns daran, dass nur von denjenigen cells !v die belegte Fläche berechnet werden muss, die auf dem Rand des einzufügenden bzw. entfernenden Rechtecks r liegen. Die cells, die komplett innerhalb von r liegen, konnten im sortierten Baum Ti in Zeit O(log n) gefunden werden. Es kann nur max. 4 cells geben, die einen Eckpunkt von r enthalten (also konstant viele) und höchstens 4s = 4 · n1/3 cells, die den Rand von r schneiden. Es sind folglich O(n1/3 ) cells zu aktualisieren. Da die cells in den Blättern des Binärbaumes Ti liegen und dieser die Höhe log n hat, können wir die Flächen in O(n1/3 · log n) armortisierter Zeit aktualisieren. Da die Vorfahren bis hin zur Wurzel von Ti in jeweils O(1) Zeit und damit insgesamt in O(log n) Zeit aktualisiert werden können, ergibt dies eine Gesamtlaufzeit von O(n1/3 · log n) armortisierter Zeit. Jetzt muss noch betrachtet werden, welche Zeit beim Einfügen bzw. Entfernen eines long-Typs (Rims oder Pillars) respektive eines short-Typs (Corners oder Floaters) innerhalb einer cell benötigt wird. Wir werden sehen, 11 dass bei der vorgestellten Datenstruktur das Einfügen / Entfernen eines longs O(log n) armotisierte und für einen short O(µ · log n) mit µ < O(n1/3 ) Zeit benötigt wird. Für jede cell legen wir 3 Bäume an: Für den Wert π benutzen wir einen Baum T (P), der die Pillars P abspeichert. Für ψ wiederum verwenden wir einen Baum T (C̃), und für ϕ einen Baum T (R). Wir könnten auch alle 3 Bäume in einem zusammenfassen, da sie sich nur im Informationsgehalt unterscheiden, aber der Einfachheit halber betrachten wir jedoch mehrere Bäume. Wird in unserer cell ein Pillar-Rechteck hinzugefügt, so müssen wir T (P) und π aktualisieren. T (R) hängt von P, C, f l und cl ab. Dies kann man sich leicht am Bild von vorhin klar machen. Und T (C̃) hängt offensichtlich von C̃ ab. Wie wir später in Abschnitt 2.3.3 sehen werden, hängt T (C) jedoch auch von den Pillars ab und muss daher unter anderem auch aktualisiert werden, wenn ein Pillar hinzugefügt wird. 2.2.1 Die Berechnung von ψ ψ werden wir über Anfragen an T (C̃), so genannte corner-queries“, bestim” men. Definition 10 Sei ∆ ⊆ [y0 , y1 ] ein y-Intervall, und sei W∆ das Rechteck [x0 , x1 ] × ∆. Dann sei ψ(∆) definiert als: ψ(∆) = Area([U (C̃) \ U (P)] ∩ W∆ ). Eine Anfrage von ψ(∆) bezeichnen wir corner-query“ bzw. als PSI-QUERY“ ” ” (siehe dazu Abschnitt 2.3.3). Wenn sich f l oder cl ändert, so ändert sich auch ψ. Genauso verhält es sich mit Änderungen an P, welches aber in der Formel selbst bereits beachtet wird. Eine Anfrage mit ∆ = [f l, cl] ergibt also unseren neuen Wert von ψ. 2.2.2 Die Berechnung von ϕ Entsprechend wie ψ berechnet sich ϕ durch ϕ = Area([U (R) \ U (P)] ∩ W∆ ). Mit der Wurzel von T (R) und den Werten f l und cl können wir ϕ in Zeit O(1) berechnen. Wie dies möglich ist, wird in Abschnitt 2.3 ausführlich erläutert. Insgesamt werden wir zum Aktualisieren von T (P), T (C̃) und T (R) jeweils O(log n) Zeit brauchen. 12 2.3 Die Bäume T (P), T (C̃) und T (R) Benötigt werden diese Bäume, um möglichst schnell die neuen Werte für ψ und ϕ zu bestimmen, um die in einer cell belegte Fläche α! berechnet zu haben. Dabei stellen wir als Anforderung, dass wir zum Einfügen bzw. Entfernen eines einzelnen Quadrates nicht länger als Zeit O(log n) brauchen. Wir werden uns zu Nutze machen, dass in einer cell nicht mehr als O(n1/3 ) viele Quadrate liegen können, um dann den jeweiligen Baum in Zeit O(n1/3 · log n) komplett neu aufzubauen. Bevor die genauen Details erklärt werden, wird erstmal eine kleine Zusammenfassung davon gegeben: 2.3.1 Übersicht Wie in Definition 10 auf Seite 12 definiert, benutzen wir T (C̃) für cornerqueries. Es gibt 2 Fälle beim Einfügen und Entfernen eines Quadrates r in einer cell: 1. Short: Es handelt sich bei r um eine Corner oder einen Floater. 2. Long: Es handelt sich bei r um ein Rim oder ein Pillar. Betrachten wir das Einfügen respektive das Entfernen eines shorts: Dann müssen die Bäume T (C̃) und T (R) aktualisiert werden. T (P) muss nicht aktualisiert werden, da kein Pillar vorliegt. Wir wissen, dass Quadrate vom Typ short“ mindestens ein Vertex innerhalb der cell ! liegen haben. ” Weiterhin ist nach Konstruktion von slabs und cells bekannt, dass jede ! höchstens 4n viele Vertices haben kann. Eventuell noch einen Vertex mehr, s2 aber der spielt in der O-Notation keine Rolle. Sei µ ≤ 4n = O(n1/3 ) als2 so die maximale Anzahl an Vertices und damit die maximale Anzahl an short-Rechtecken innerhalb von !. Wenn es sich bei r um ein Corner-Quadrat handelt, r also aus der Menge C ist, können wir daher C̃ in Zeit O(µ · log n) neu erzeugen. Dabei werden alte Rechtecke aus C̃ gelöscht (in Zeit O(1)), und alle aktuell vorhandenen Quadrate neu eingefügt. Da es sich um maximal µ viele Quadrate handeln kann, kommen wir also auf die zuvor erwähnte Laufzeit von O(µ · log n). Wenn es sich jedoch bei r um ein Floater-Quadrat handelt, so können wir genau wie bei C̃ die Menge R und den Baum T (R) in der selben Zeit O(µ · log n) neu aufbauen. Zusammengenommen ist damit eine Laufzeit von O(n1/3 ·log n) gegeben, da µ ∈ O(n1/3 ). Betrachten wir nun das Einfügen respektive das Entfernen eines longs: Dann handelt es sich bei diesem Quadrat r um ein Quadrat, welchen keinen 13 Vertex inerhalb der cell ! liegen hat. Dann gibt es folglich Änderungen an π, ψ und / oder ϕ (siehe Abbildung 6 auf Seite 9). Wenn r ein Pillar ist, so werden zuerst T (P) und dann T (C̃) und T (R) aktualisiert. Danach folgt eine Aktualisierung der Werte π, ψ und ϕ In beiden Fällen ändert sich etwas an der in der cell belegten Fläche α! . Insgesamt wird dafür jedoch nicht mehr als O(n1/3 · log n) Zeit benötigt. Kommen wir nun also zu den genauen Details. 2.3.2 Verarbeitung von T (P) Die Verwaltung von T (P) ist relativ einfach, da wir diesen Baum lediglich dazu benötigen, um den Wert π zu bestimmen. Auch hier wird das schöne Verfahren von Divide & Conquer dazu verwendet. Es geht darum, die xIntervalle aller Pillars von cell ! zu berechnen. Dazu betrachten wir immer 2 Pillars bzw. 2 Vereinigungen von Pillars, und vereinigen sie im Vaterknoten zu einem neuen Wert. Jeden Knoten v ist verknüpft mit • δv , das x-Intervall des Pillars bzw. das Intervall der Vereinigung von Pillars • !v = δv × [y0 , y1 ] ein Rechteck. Sei Pv ⊆ P, so dass alle Elemente aus Pv das Intervall δv komplett enthält, + ∗ jedoch nicht das Intervall vom Vaterknoten. Sei weiterhin Pv = w Pw , wobei w alle Nachfahren vom Knoten v sind, v selbst mit eingeschlossen. Im Knoten v selbst speichern wir • |Pv | • π(v), die Länge des Teilstücks von δv , welches von den Pillars p ∈ P∗v überlappt wird. Die Wurzel von T (P) entspricht dann dem gesuchten Wert π. Einfügen können wir einen neuen Pillar P wie folgt: Zuerst muss der Knoten neu eingefügt werden und setzen π(v) = -δv -. Von diesem Knoten aus aktualisieren wir die Vaterknoten. Da es nur log n Vaterknoten gibt, benötigen wir hierfür also nur O(log n) Zeit. Für den Baum T (P) definieren wir noch direkt eine pillar-length query“, ” die wir für die Verwaltung von T (C̃) benötigen werden. Diese Anfrage gibt uns für ein Intervall I einen Wert π(I) und arbeitet rekursiv. Hierfür kurz ein wenig Pseudo-Code: Die Laufzeit dieses Algorithmus ist O(log n), da der Baum eine Tiefe von O(log n) hat und wir per Tiefensuche bis zu den Blattknoten laufen, wo wir aktualisieren müssen (und die direkten Kindknoten der dabei besuchten inneren Knoten). 14 Algorithmus 2.1 PI-QUERY(v, I) // Gib Länge 0 zurück, falls I leer ist if I = ∅ then return 0 end if if δv ⊆ I then return π(v ) end if if I ⊆ δv and |Pv | ≥ 1 then // Länge von I zurückgeben return |I | end if // Rekursiver Aufruf für linken und rechten Nachfolger return PI-QUERY(L(v), I ∩ δL(v) ) + PI-QUERY(R(v), I ∩ δR(v) ) 2.3.3 Verarbeitung von T (C̃) Betrachten wir nun den Baum T (C̃). Dieser Baum wird ähnlich dem Baum T (P) gehandhabt. Ähnlich wie die Definition von P∗v definieren wir nun für + ∗ i = 0, 1, 2, 3 C̃iv = w C̃iw , wobei mit w auch hier wieder alle Nachfahren vom Knoten v gemeint ist, v selbst mit eingeschlossen. Weiterhin ist C̃iv ⊂ (C ∈ C̃i ), so dass δv ⊆ IC ⊂ δp(v) . Definition 11 Sei !v gegeben, und Pi ein staircase-Polygone, welches !v schneidet. Dann ist Ji (v) definiert als das kleinste vertikale Segment, welches die y-Koordinaten von allen horizontalen Kanten von Pi beinhaltet. Ji (v) ist leer oder besitzt nur ein Element, wenn !v nur eine horizontale staircase-Kante von Pi schneidet (also nicht die horizontale Kante, die mit einer vertikalen den apex bildet). Da es sich beim staircase-Polygon um eine y-monotone Folge von Kanten handelt, sind die Elemente von Ji (v) aufsteigend oder absteigend in den y-Koordinaten sortiert. Für jeden Knoten v von T (C̃) speichern wir folgende Informationen: • ξi (v) = Area([U (C̃∗iv ) \ U (P∗v )] ∩ !v ) für i = 0, 1, 2, 3 • Ji (v) Wenn die Menge C̃iv *= ∅ ist, dann besteht sie aus einem einzigen Element: Einem Rechteck Civ . Definition 12 Sei das Rechteck Civ gegeben. Dann ist χiv definiert als diejenige y-Koordinate einer horizontalen Kante von Civ , welche im inneren von !v liegt, also entweder die obere oder untere Kante des Rechtecks. 15 Definition 13 conv(X) bezeichne das kleinste Intervall, welches X enthält. Definition 14 Sei das Rechteck C gegeben. Dann bezeichne Ht(C) die Höhe von C. Für innere Knoten v sei 0 ξi (v) = (-δv - − π(v)) · Ht(Civ ) ξi (L(v)) + ξi (R(v)) Ji (v) = " , falls Pv = * ∅ , falls Pv = ∅, C̃iv *= ∅ , sonst [ξiv , ξiv ] conv(Ji (L(v)) ∪ Ji (R(v))) , falls C̃iv *= ∅ , sonst (3) (4) Wie man sieht, hängt ξi (v) von π(v) ab. Wir aktualisieren, wie zuvor erwähnt, den Baum T (C̃) also auch, wenn ein Pillar hinzugefügt wird, und das obwohl Pillars in diesem Baum nicht gespeichert werden. Betrachten wir also zunächst den Fall, dass ein Pillar P eingefügt oder entfernt wird. Zunächst wurde dann der Baum T (P) aktualisiert. Nachdem diese Aktualisierung abgeschlossen ist, können wir daher ξi (v) und Ji (v) mit Hilfe von Gleichung 3 und Gleichung 4 aktualisieren. Dabei gehen wir wie schon bei T (P) nach dem Verfahren vor, dass wir bei den Blattknoten anfangen, und bis zur Wurzel alle beeinflussten Vaterknoten aktualisieren. Wenn es sich bei den einzufügenden bzw. zu entfernenden Quadrates r wiederum um ein Corner-Typ handelt, gehen wir anderweitig vor: Zuerst speichern wir C ∈ C̃i als Rechteck Civ für alle Knoten v ∈ κ(IC ). Dann können wir wieder gewohnt uns von unten nach oben durch den Baum durcharbeiten und für die betroffenen Knoten ξi (v) und Ji (v) aktualisieren. Da C̃iv *= ∅, wird für die Aktualisierung der einelementigen Menge keine Rekursion benötigt. Dies kann man direkt an Gleichung 3 und Gleichung 4 sehen. Kommen wir nun zu den Details der PSI-QUERY“, oder auch corner” ” area query“ genannt. Wir erinnern uns an die Definition 10 auf Seite Seite 12. Da wir bis zu 4 disjunkte staircase-Polygone haben, ist ψ(∆) = Area([U (C̃) \ U (P)] ∩ W∆ ). das gleiche wie ψ(∆) = ,3 i=0 ψi (∆) mit ψi (∆) = Area([U (C̃i ) \ U (P)] ∩ W∆ ). 16 Es soll an dieser Stelle genügen, die Berechnung von ψ0 (∆) zu zeigen, da die Berechnung der anderen 3 Werte analog gehen. Sei ∆ gegeben als ∆ = [α, β] ⊆ [y0 , y1 ]. Betrachten wir C̃0 , die Menge an Rechtecken der Corner-Quadrate, die ein staircase-Polygon in der unteren linken Ecke von der cell ! bilden. Sei C̃0 (∆) ⊆ C̃0 diejenigen Rechtecke aus C̃0 , dessen obere Kante im Intervall ∆ liegen. Die Rechtecke aus C̃0 (∆) sind anschaulich alle zusammenhängend, und ihre äußere Kontur bildet ein staircase-Polygon P0 (∆), welches ein Teilstück des Polygons P0 ist (siehe Abbildung 10). Pillars ψ0 (Λ) β ∆ P0 (Λ) RL ∆ α RB xL xR x0 xR xL IL IB Abbildung 10: Veranschaulichung der Variablen und Flächen. Sei xL die x-Koordinate des linken Randes, und xR die x-Koordinate des rechten Randes von P0 (∆). Seien weiterhin das Intervall IL = [x0 , xL ] und das Intervall IB = [xL , XR ] definiert. Weiterhin definieren wir die Rechtecke RL = IL × ∆ und RB = IB × [y0 , α]. Dann ist U (C̃0 ) ∩ W∆ = [P0 (∆) \ RB ] ∪ RL . Da RL und P0 (∆) nach Definition disjunkt sind, und RB ⊆ P0 (∆), gilt: ψ0 (∆) = Area(P0 (∆) \ U (P)) − Area(RB \ U (P)) + Area(RL \ U (P)) = Area(P0 (∆) \ U (P)) − (xR − xL − π(IB )) · (α − y0 ) + (xL − x0 − π(IL ) · (β − α) (5) Dabei können π(IB ) und π(IL ) mit 2 pillar-length-queries (PI-QUERY) beim Baum T (P) mit den Werten IB und IL berechnet werden (siehe Algorithmus 2.1 auf Seite 15). 17 Die Laufzeit von PSI-QUERY“ ist O(log n), aus dem gleichen Grund ” wie schon bei T (P), nämlich die Tiefe des Baumes von O(log n) und der angewendeten Tiefensuche. 2.3.4 Verarbeitung von T (R) Nachdem nun die Bäume T (P) und T (C̃) in Zeit O(log n) aktualisiert werden konnten bzw. auch die Queries diese Laufzeit aufwiesen, würde man dies für den Baum T (R) ebenfalls erwarten. Und dem ist auch so. Diesen Baum müssen wir aktualisieren, wenn ein Pillar oder ein Floater hinzugefügt oder entfernt wird. Für diesen Baum gehen wir zuerst analog zu den anderen Bäumen vor. Für jeden Knoten v ∈ T (R) sei Rv definiert als Teilmenge von denjenigen Rechtecken aus + R, deren x-Projektion δv beinhaltet, jedoch nicht δp(v) . Sei weiterhin R∗v = w Pw , wobei w alle Nachfahren vom Knoten v sind, v selbst mit eingeschlossen (dies ist bisher alles analog zu T (P)). Bei jedem Knoten v von T (R) speichern wir folgende Informationen: • ϕ(v): Wie zuvor schon definiert ist hiermit die Fläche U (Rv∗ ) ∩ !v gemeint, die von keinem Typ long bedeckt wird. • h(v): Die Länge der von den Rechteckes aus Rv bedeckten linken Kante von !v , ohne Upper- und Lower-Rims. • λf (v): Länge der von Stalagmiten aus Rv∗ bedeckten floor, aber ohne Pillars. • λc (v): Länge der von Stalagtiten aus Rv∗ bedeckten ceiling, aber ohne Pillars. • f l(v): Die aktuelle y-Koordinate vom obersten floor. • cl(v): Die aktuelle y-Koordinate vom untersten ceiling. Wie bereits in Abschnitt 2.2.2 auf Seite 12 erklärt, dient der Baum T (R) dazu, den Flächenwert ϕ zu berechnen. In der Wurzel von T (R) wird also der fertig berechnete Flächenwert ϕ stehen. Betrachten wir zunächst innere Knoten v: Seien w und z die Kindknoten von v. Dann gelten folgende Formeln: ϕ(v) = " 0 ϕ(w) + ϕ(z) + (-δv - − π(v)) · h(v) 18 , falls Pv *= ∅ , sonst (6) 0 -δv λf (v) = λf (w) + λf (z) 0 -δv λc (v) = λc (w) + λc (z) , falls Pv = * ∅ , falls Pv = ∅, Rv ∩ Sm *= ∅ , sonst (7) , falls Pv = * ∅ , falls Pv = ∅, Rv ∩ St *= ∅ , sonst (8) T (R) hat immer die korrekten Werte von λc (v) und λf (v) bei allen Knoten, aber f l(v), cl(v), ϕ(v) und h(v) könnten falsch sein, da eine Aktualisierung von floor und ceiling (also von Upper- und Lower-Rims) nicht jeden Knoten von T (R) erreicht. Dies kann beispielsweise passieren, wenn ein Lower Rim hinzugefügt wird, und dieser zwischen dem alten höchsten Lower Rim und den unterstern Kanten von Λ liegen. Es wäre mitunter auch zu kostenintensiv, dann alle Knoten zu aktualisieren, daher werden folgende 2 Invarianten benutzt: • (I1) Für jeden Knoten v ∈ T (R) liegt keine y-Koordinate der horizontalen Kanten der Rechtecke aus Rv∗ zwischen f l und f l(v) bzw. zwischen cl und cl(v). • (I2) Unter der Voraussetzung, dass floor bzw. ceiling bei f l(v) bzw. cl(v) liegen, hat ϕ(v) den Wert der Fläche U (Rv∗ ) ∩ !v , die nicht von long-Typen bedeckt wird. Die Informationen müssen auch nicht bei jedem Knoten v aus T (R) korrekt sein. Es genügt, wenn sie bei denen korrekt sind, bei denen wir Änderungen vornehmen, da auch hier nur von einem hinzugefügten Blattknoten bis hin zur Wurzel hochgerechnet wird. Viele Knoten im Baum werden daher nicht berührt. Für diejenigen Knoten, dir wir aktualisieren müssen, verwenden wir folgende 2 Funktionen, um die Werte für f l(v) und cl(v) zu aktualisieren, bevor wir mit den anderen in v gespeicherten Werten arbeiten. Algorithmus 2.2 ADJUSTFLOOR(v) ϕ(v ) = ϕ(v ) − λf (v ) · [fl − fl (v )] if R(v ) ∩ Sm *= ∅ then h(v ) = h(v ) − [fl − fl (v )] end if fl (v ) = fl Mit dem Aufruf von diesen Funktionen haben wir die Werte f l(v), cl(v), ϕ(v) und h(v) auf den aktuellen Stand gebracht und damit korrigiert. 19 Algorithmus 2.3 ADJUSTCEILING(v) ϕ(v ) = ϕ(v ) − λc (v ) · [cl − cl (v )] if R(v ) ∩ St *= ∅ then h(v ) = h(v ) − [cl − cl (v )] end if cl (v ) = cl Da auch dieser Baum beim Einfügen oder Entfernen von Pillars beeinträchtigt wird (da seine Flächen explizit mit Pillars geschnitten wird), betrachten wir uns kurz die Vorgehensweise hierbei, da sie analog zu der Vorgehensweise von T (P) und T (C̃) ist: Wir arbeiten uns zuerst von der Wurzel aus nach unten durch und suchen entsprechend einen Platz, wo wir den Knoten einfügen bzw. wo wir ihn entfernen müssen und korrigieren dabei mit Aufruf der beiden Funktionen ADJUSTFLOOR und ADJUSTCEILING die Werte der besuchten Knoten. Sobald wir in den Blattknoten angekommen sind, laufen wir wieder hoch zur Wurzel und aktualisieren dabei bei jedem besuchten Knoten die Werte f l(v), cl(v), ϕ(v), λc (v) und λf (v) mit Hilfe von Gleichung 6, Gleichung 7 und Gleichung 8. Zu guter Letzt müssen wir uns noch um das Einfügen bzw. Entfernen von Floatern kümmern. Auch hier laufen wir von der Wurzel zu den Blattknoten und aktualisieren mit ADJUSTFLOOR und ADJUSTCEILING die Werte der besuchten Knoten. Danach gehen wir für jeden besuchten Knoten, für den R ∈ Rv ist, folgende Schritte durch: Sei κv = δv × [f l, cl] und r = Ht(R ∩ κv ). Wenn wir R einfügen wollen: • Setze h(v) = h(v) + r. • Setze ϕ(v) = ϕ(v) + (-δv - − π(v)) · r. • Setze λf (v) = -δv - − π(v), wenn R ein Stalagmit ist. • Setze λc (v) = -δv - − π(v), wenn R ein Stalagtit ist. Das Löschen von R geht analog, wobei λf (v) bzw. λc (v) auf 0 gesetzt wird, je nachdem, ob ein Stalagmit oder ein Stalagtit vorliegt. Beim zurücklaufen“ ” bis zur Wurzel werden die Werte wieder gemäß Gleichung 6, Gleichung 7 und Gleichung 8 neu berechnet. Für dieses Verfahren wird genau wie bei T (P) und T (C̃) nur O(log n) Zeit benötigt, da der Baum die Tiefe log n hat und wir nur die zu aktualisierenden Knoten (und dessen direkten Kinder) besuchen. 20 3 Zusammenfassung Wir haben n Würfel im R3 gegeben. Zur Berechnung des Volumens wird eine Sweep-Ebene verwendet, die von z = −∞ bis z = +∞ den Raum durchläuft. Dabei treten 2n Ereignisse auf, in denen die Schnittfläche neu berechnet werden muss. Bei jedem dieser Ereignisse ändern sich höchstens 4 · n1/3 cells, die auf dem Rand vom einzufügenden oder zu entfernenden Rechtecks r liegen. Cells, die im Inneren von r liegen, konnten wir mit Hilfe von Ti und unseren slabs Bi in O(log n) Zeit bestimmen. Wir haben gesehen, dass die in der cell belegte Fläche α! für cells !, die auf dem Rand von r liegen, mit Hilfe unserer Baumstruktur und der Unterscheidung von Rims, Pillars, Corners und Floaters in Zeit O(n1/3 · log n) berechnet werden kann. Die Aktualisierung der Bäume benötigt zwar lediglich eine Zeit von O(log n), jedoch sind es pro cell bis zu n1/3 Quadrate, die die cell schneiden und für jedes dieser Quadrate muss diese Aktualisierung durchgeführt werden. Bei den gegebenen 2n Ereignissen folgt also eine Gesamtlaufzeit des Algorithmus von O(2n · n1/3 · log n) = O(n · n1/3 · log n) = O(n4/3 · log n) Zeit. Literatur [1] M. S. Pankaj K. Agarwal, Haim Kaplan. Computing the volume of the union of cubes. 21