Theorie und Praxis geometrischer Algorithmen Sweep Line Algorithmus Roman Adam Einführung Gegeben: Liniensegmente si, dargestellt durch ihre linken und rechten Endpunkte li und ri mit 0 i n. Man betrachtet die Endpunkte und Schnittpunkte als Knoten eines Graphen und die Segmentabschnitte zwischen diesen Punkten als Kanten. Gesucht ist ein Algorithmus, der die Schnittpunkte berechnet und aus den Punkten und Segmenten einen Graphen aufbaut. Konstruktion eines planaren Graphen: k v L a R v` k` Man speichert die Kante a in einer speziellen Form ab: k previous from to left right v v` L R next k` Einfach-Lösung: Alle Segmente paarweise auf Schnittpunkte testen, gefundene Schnittpunkte speichern und dann Graph berechnen. Problem: schlechte Laufzeit: O(n2) • • nicht besonders effizient, falls wenige Schnittpunkte man möchte den Graphen während der SchnittpunktBerechnung konstruieren gesucht: Algorithmus, der nicht nur von der Anzahl der Segmente, sondern auch von der Anzahl der Schnittpunkte abhängt Output - sensitiv Idee: Man braucht nur Segmente zu testen, die ‚nah‘ beieinander liegen. Aber was ist ‚nah‘? Wir projezieren die Segmente auf die x-Achse y x Man sieht: Durch Projektion der Segmente auf die x-Achse erhält man einen Hinweis, ob zwei Segmente ‚nah‘ beieinander sind. Idee: Man bewegt eine (imaginäre) Linie parallel zur y-Achse von links nach rechts. Sweep Line („Fegelinie“) Man betrachtet nur die Segmente, die an einer bestimmten Stelle x diese Linie schneiden und testet die Segmente auf Schnitt, die nebeneinander liegen. Aus einem statischen zweidimensionalen wird ein dynamisches eindimensionales Problem. Frage: Wie findet man heraus, welche Segmente die Sweep Line schneiden? y x • Die Sweep Line muss nur an diskreten Punkten, den Endpunkten der Segmente ‚anhalten‘. • Man betrachtet die x-Achse als Zeitachse und bezeichnet einen solchen diskreten Punkt als Ereignis. Diese Ereignisse werden in einer besonderen Datenstruktur gespeichert, der Ereignis- oder X-Struktur. Diese Datenstuktur wird also zu Beginn mit den Endpunkten der Segmente initialisert, die in aufsteigender Reihenfolge nach ihren x-Koordinaten geordnet sind. Wie werden die Segmente behandelt, die die Sweep Line schneiden? Die Segmente, die zu einem bestimmten Zeitpunkt die Sweep Line schneiden, werden in der Status- oder Y-Struktur gespeichert und zwar in aufsteigender Reihenfolge ihrer yKoordinaten. Die Y-Struktur ist zu Beginn leer. x1: s3, s1, s4, s2 x4: s3, s2,s5 x2: s3, s4, s2 x5: s2, s3,s5 x3: s3, s2 x6: s2, s5,s3 y s5 s2 s4 s1 s3 x x1 x2 x3 x4 x5 x6 Sweep Line Algorithmus Voraussetzung: • x-Koordinaten der Schnitt- und Endpunkte sind paarweise verschieden • Länge der Segmente > 0 - nur echte Schnittpunkte keine Linien parallel zur y-Achse keine Mehrfachschnittpunkte keine überlappenden Segmente Beim Sweep können folgende Ereignisse eintreten, die die Ordnung der Segmente in der Y-Struktur verändern und eine Aktualisierung der X-Struktur erfordern: (1) Die Sweep Line stösst auf den linken Endpunkt eines Liniensegments (2) Die Sweep Line stösst auf den rechten Endpunkt eines Liniensegments (3) Die Sweep Line stösst auf den Schnittpunkt zweier Liniensegmente Frage: Wie soll man die Schnittpunkte kennen, wenn man sie doch suchen soll? y s5 s2 s4 s1 p s3 x1 x2- x2 x • kurz vor einem Schnittpunkt sind die beiden Segmente benachbart • durch die Ordnung in der Y-Struktur kann man dies leicht erkennen Man muss zwei Liniensegmente sofort auf Schnitt testen, wenn sie in der Y-Struktur Nachbarn werden. Durch diesen Test, kann man alle Schnittpunkte spätestens kurz vor ihrem Auftreten erkennen. Lemma 1: Wenn zwei Liniensegmente einen echten Schnittpunkt haben, wo werden sie unmittelbar vorher direkte Nachbarn in der Ordnung längs der Sweep Line. y s5 s2 s4 U(p) s1 p s3 x1 x2- x2 x Beweis: Sei p = (x2, y2) echter Schnittpunkt von s2 und s3. Weil nach Voraussetzung kein anderes Liniensegment den Punkt p enthält, gibt es ein > 0, s.d. die Umgebung U(p) nur von s2 und s3 geschnitten wird => zu jedem Zeitpunkt x (x2 - , x2) sind s2, s3 direkte Nachbarn in der Y-Struktur, spätestens nach dem letzten Ereignis vor Erreichen von p. • Wie sind die Ereignisse zu behandeln? Linker Endpunkt: • neues Segment s richtig in die Y-Struktur einfügen • s mit Vorgänger si und Nachfolger sk auf Schnitt testen • gefundenen Schnittpunkt als zukünftiges Ereignis an der richtigen Stelle in die X-Struktur einfügen sk s si Rechter Endpunkt: • Segment s aus Y-Struktur entfernen • Vorgänger si und Nachfolger sk sind jetzt selbst Nachbarn und werden sofort auf Schnitt getestet. sk si s Schnittpunkt: • die beiden Linien si und sk, die sich schneiden, vertauschen ihre Position in der Y-Struktur • der alte Vorgänger von si (sh) ist jetzt Vorgänger von sk • der alte Nachfolger von sk (sm) ist jetzt Nachfolger von si • somit müssen sk mit sh und si mit sm auf einen gemeinsamen Schnittpunkt getestet werden sm sk si sh Sweep-Line-Algorithmus • initialisiere die Ereignis-Struktur mit den schon bekannten Ereignissen, den Endpunkten der Segmente, geordnet nach aufsteigender x-Koordinate • initialisiere die (leere) Y-Struktur • nimm das erste Ereignis aus der X-Struktur und behandele es wie angegeben • lies solange Ereignisse und behandle sie wie angegeben, bis die X-Struktur leer ist /* Initialisierung */ Init(); /* Sweep und Konstruktion des Graphen */ while( X 0 ) { Ereignis := NächstesEreignis( X); switch( Ereignis) { case LinkerEP: FügeEin(Y, Seg); VSeg := Vorg( Y, Seg); TesteSchnittErzeugeEreignis( VSeg, Seg); NSeg := Nachf( Y, Seg); TesteSchnittErzeugeEreignis( Seg, NSeg); case RechterEP: VSeg := Vorg( Y, Seg); NSeg := Nachf( Y, Seg); Entferne( Y, Seg); TesteSchnittErzeugeEreignis( VSeg, NSeg); case Schnittpunkt: Vertausche( Y, USeg, OSeg); VSeg := Vorg( Y, OSeg); TesteSchnittErzeugeEreignis( VSeg, OSeg); NSeg := Nachf( Y, USeg); TesteSchnittErzeugeEreignis( USeg, NSeg); } } Die X-Struktur und die Y-Struktur können als balancierter binärer Suchbaum implementiert werden. Aufbau: O(n log n) insert, lookup, delete, search: O(Höhe) = O(log n) Korrektheit: Die Korrektheit des beschriebenen Sweep Line Algorithmus unter den angegebenen Voraussetzungen folgt aus Lemma 1. Laufzeit: Lemma: Mit dem angegebenen Verfahren kann man die k echten Schnittpunkte von n Liniensegmenten in Zeit O((n+k)log n) berechnen. Beweis: Der Algorithmus beginnt mit der Konstruktion der X-Struktur. Ist diese als balancierter binärer Suchbaum implementiert, benötigt dies O(n log n) Zeit. Dann werden die Ereignisse abgearbeitet. Insgesamt gibt es 2n+k Ereignisse. Weil keines von ihnen mehrfach in der XStruktur vorkommt (das Einfügen eines schon vorhandenen Ereignisses wird nicht erlaubt), sind niemals mehr als O(n2) Ereignisse in der Ereignis-Struktur gespeichert. Jeder Zugriff auf die Ereignis-Struktur ist also in Zeit O(log n2) = O(log n) ausführbar. Die while-Schleife wird (2n+k)-mal durchlaufen; bei jedem Durchlauf werden konstant viele Operationen auf der X- oder der Y-Struktur durchgeführt. Line Sweep für degenerierte Segmente (am Beispiel LEDA) Um gleiche x-Koordinaten für die Ereignis-Punkte zuzulassen, ändern wir den Aufbau der Sweep Line: Wir definieren die Sweep Line durch einen Punkt p_sweep = (x_sweep, y_sweep). Die Sweep Line L ist nun ein vertikal aufsteigender Strahl bis zum Punkt (x_sweep + 2, y_sweep + ), gefolgt von einem horizontalen Teil bis zum Punkt (x_sweep – 2, y_sweep + ), gefolgt von einem vertikal steigenden Strahl. L e b h s4 s5 s7 a i s9 s3 s2 f g p_sweep s1 s6 c s8 d Man beachte, dass kein Endpunkt irgendeines Segmentes auf L liegt, dass keine zwei Segmente L im gleichen Punkt schneiden, ausser sie überlappen sich und dass kein nicht-vertikales Segment den horizontalen Teil von L schneidet. Aufbau X-Struktur Die X-Struktur enthält einen Eintrag für jeden Endpunkt rechts der Sweep Line und einen Eintrag für jeden Schnittpunkt zwischen Segmenten, die in der Y-Struktur benachbart sind. Jeder Eintrag enthält zudem einen Link auf einen Eintrag in der Y-Struktur oder nil. Ist das Ereignis ein rechter Endpunkt, so zeigt der Link auf das zugehörige Segment. Ist es ein Schnittpunkt, so zeigt der Link auf eines der Segment, die sich in diesem Schnittpunkt schneiden. Bei einem linken Endpunkt, ist der Link nil. L e b h s4 s5 s7 a i s9 s3 s2 f g p_sweep s1 s6 c s8 d X-Struktur: <a,sit4>,<b,sit4>,<c,sit1>,<d,nil>,<e,sit9>,<f,sit1>,<g,sit2>,<h,sit3>,<i,sit1> Aufbau Y-Struktur Die Y-Struktur enthält neben dem Segment, dass die Sweep Line schneidet, einen Verweis auf ein anderes Element, dass an der gleichen Stelle die Sweep Line berührt (überlappende Segmente) oder einen Verweis auf einen Schnittpunkt (also ein Ereignis in der X-Struktur), falls solche vorhanden sind, sonst nil. Die Leda-Funktion hat zudem zwei Elemente + und – als Begrenzer der Struktur nach oben und unten. Ordnung in der YStruktur Die Segmente werden in lexikographischer Ordnung sortiert. Überlappende Segmente werden nach ihrer ID-Nummer sortiert. L e b h s4 Y-Struktur s5 <s3,nil> s7 a i s9 s3 <s4,xita> <s9,nil> s2 f <s2,nil> g p_sweep <s8,xitf> <s1,sit8> s1 s6 c s8 d X-Struktur: <a,sit4>,<b,sit4>,<c,sit1>,<d,nil>,<e,sit9>,<f,sit1>,<g,sit2>,<h,sit3>,<i,sit1> Die Abarbeitung eines Ereignisses p, also eines Knotens geschieht nun so: 1. 2. 3. 4. 5. 6. 7. Füge einen Knoten v an Position p zum Ausgabe-Graph. Erstelle aus allen Segmenten in der Y-Struktur, die die Sweep Line in p schneiden, eine Substruktur, die möglicherweise leer ist. Füge für jedes Segment in der Substruktur eine Kante zum Graphen hinzu. Lösche alle Segmente, die in p enden, aus der Y-Struktur. Aktualisiere die Ordnung in der Substruktur der Y-Liste, dabei werden überlappende Segmente nach ihrer ID geordnet. Dies kann als bewegen der Sweep Line über den Punkt p hinaus angesehen werden. Füge alle Segmente zur Y-Struktur, die in p beginnen. Erzeuge Ereignisse für alle Segmente in der Y-Struktur, die durch die letzten Aktionen Nachbarn geworden sind, teste also auf Schnitt und aktualisiere die Verkettung von X- und Y-Struktur. Y-Struktur s1 v s2 s4 <+,nil> <s1,nil> s5 s3 <s2,xitv> <s4,xitv> <s5,xitv> (v,u1),(v,u2),(v,u4),(v,u3),(v,u5) <s3,sit5> <-,nil> Durch die Verbindung von X- und YStruktur werden die m eingehenden Knoten vom Leda-Sweep in O(m) abgearbeitet. Laufzeit: O((n+s)log(n+m)+m) n = Zahl der Segmente s = Zahl der Knoten m = Zahl der Kanten (n+s) Schleife über alle Ereignisse log(n+m) Operationen im Suchbaum m Bearbeitung der Substruktur