Algorithmische Geometrie Vorlesung WS 2002/03 Tübingen Olaf Delgado-Friedrichs Olaf Delgado-Friedrichs, Universität Tübingen, WSIAB, Sand 14, 72076 Tübingen, Germany E-mail address: [email protected] KAPITEL 1 Algorithmische Geometrie - was ist das? Na klar, in der algorithmischen Geometrie geht es um die Entwicklung und Analyse von Algorithmen für geometrische Probleme. Um ein Gefühl dafür zu bekommen, welche Art von Problemen das sind, beginnen wir mit einigen kleinen Beispielen. 1. Beispiel: Schnittpunkte Wir betrachten eine Anzahl von Geradenabschnitten in der Ebene, wie hier im Bild: Aus der Schule wissen wir (hoffentlich!) noch, wie man herausfindet, ob ein beliebiges Paar dieser Strecken sich schneiden und in diesem Fall den Schnittpunkt bestimmt. Diese Berechnung hängt nicht von den übrigen Strecken ab, hat also für unsere Zwecke Zeit- und Speicherkomplexität O(1). Um alle Schnittpunkte zu bestimmen, können wir jede Strecke mit jeder anderen schneiden und brauchen dazu bei n Strecken offensichtlich O(n2 ) Operationen. Aber geht das nicht auch schneller? Um Probleme dieser Art zu lösen, braucht man zweierlei. Zunächst einmal muss man die geometrische Situation verstehen und mit den grundlegenden geometrischen Objekten umgehen können. Ausserdem aber muss man wissen, wie man Algorithmen formuliert und analysiert und muss grundlegende Datenstrukturen und Algorithmen kennen und anwenden können. Natürlich kommt es auch vor, dass so wie hier tatsächlich jedes Paar von Strecken einen Schnittpunkt hat: 3 4 1. ALGORITHMISCHE GEOMETRIE - WAS IST DAS? Das legt nahe, ein Verfahren zu suchen, dessen Laufzeit nicht nur von der Komplexität der Eingabe, sondern auch von der der Ausgabe, hier also der Anzahl k der Schnittpunkte abhängt. Ein einfache Idee hierzu ist die folgende: man lege ein Lineal senkrecht auf die Abbildung und schiebe es langsam von links nach rechts über das Bild. Nun achte man einfach nur darauf, wie und wo sich die Anordnung der Schnittpunkte der abgebildeten Linien mit der rechten Linealkante verändert. Es gibt genau 2n+k solche Ereignisse: an n Linealpositionen kommt eine neue Strecke hinzu und an ebenfalls n Positionen verschwindet eine Strecke. An k Positionen schneiden sich Linien, und zwar nur solche, die zuvor entlang der Linealkante benachbart waren. Es reicht also offenbar, bei jedem Ereignis alle Paare von Strecken auf Schnittpunkte zu untersuchen, die nach dem Ereignis benachbart sein werden, es zuvor aber nicht waren. Wie wir später sehen werden, kann man einen auf dieser Idee basierenden Algorithmus mit der Laufzeit O(n log n + k log n) und dem Speicherbedarf O(m) formulieren. Das ist im schlimmsten Fall, nämlich 2. BEISPIEL: MUSEUMSPROBLEME 5 bei k = O(n2 ), sogar ein klein wenig schlechter als der naive Algorithmus, wird aber in den allermeisten praktischen Anwendungen wesentlich besser sein. In der algorithmischen Geometrie spielen Verfahren, die auf dem oben angedeuteten Prinzip beruhen, eine große Rolle. Man nennt sie auch Plane-Sweep-Algorithmen. Das oben beschriebene Segmentschnittproblem hat z.B. Amwendungen in der Computergrafik bei Hidden Line Verfahren, oder auch bei geographischen Informationssystemen. 2. Beispiel: Museumsprobleme Aus großen Museen werden regelmäßig Kunstwerke gestohlen. Um dem entgegenzuwirken, könnte man zum Beispiel auf die Idee kommen, eine Anzahl von Videokameras zu installieren, deren Bilder dann in einem Kontrollraum kontinuierlich beobachtet werden. Um dem Personal die Arbeit zu erleichtern, ist es zweckmäßig, eine möglichst kleine Zahl von Kameras aufzubauen, die aber trotzdem jeden Winkel des zu bewachenden Raums im Blickfeld haben sollen. Der Einfachheit halber nehmen wir an, dass die Räume zwar so wie der hier gezeigte beliebig verwinkelte Grundrisse haben, aber keine gewölbten Wände oder Abweichungen vom Grundriss vorkommen. Auch sollen keine Hindernisse im Raum die Sicht behindern. Wir betrachten einen solchen Ausstellungsraum also als einfach zusammenhängendes Polygon — ein ebenes Vieleck ohne Löcher — und suchen nach der kleinsten Anzahl von Standorten, von der aus jeder Punkt im Inneren dieses Polygons sichtbar ist. Das wiederum heisst nichts weiter als das jeder Punkt mit einem dieser Standpunkte durch eine gerade Linie verbunden werden kann, die ganz im Inneren des Polygons liegt. Hier ein Beispiel für einen Standpunkt und sein Sichtfeld: 6 1. ALGORITHMISCHE GEOMETRIE - WAS IST DAS? Hier wurde ein zweiter Standpunkt hinzugefügt: Man sieht jetzt leicht, dass man in diesem Beispiel mit 3 Kameras auskommen wird. Mit weniger als dreien scheint es aber nicht zu gehen (wie kann man das beweisen?). Wie sich leider herausstellt, ist das Problem, die minimale Anzahl von Standpunkten für ein beliebiges einfach zusammenhängendes Polygon zu finden, NP-schwer. Wie sieht es aber zum Beispiel mit oberen Schranken aus? Ein möglicher Ansatz hierzu ist, das Polygon in einfachere zu zerlegen, für die man die Anzahl nötiger Standorte sofort ablesen kann. Der oben gezeigten Grundriss beispielsweise lässt sich, ohne dass man zusätzliche Ecken braucht, in Dreiecke zerlegen: Zur Beobachtung eines Dreiecks, oder sogar allgemeiner jedes konvexen Polygons, reicht offenbar ein einziger Standort aus. Anscheinend 3. BEISPIEL: WIRKUNGSBEREICHE 7 lässt sich jedes noch so komplizierte Polygon tatsächlich ohne zusätzliche Ecken triangulieren, das heisst, in Dreiecke zerlegen und die Anzahl dieser Dreiecke scheint immer n−2 zu sein. Wenn wir diese beiden Aussagen nun allgemein beweisen könnten, wüssten wir immerhin, dass die maximale Anzahl nötiger Standorte höchstens linear in n ist. Mit etwas mehr Mühe werden wir später sogar zeigen, dass für ein beliebiges Polygon mit n Ecken immer bn/3c Standorte ausreichen. Andererseits kann man Polygone konstruieren, die mindestens bn/3c Standorte benötigen, wie das folgende Beispiel zeigt: Die dunklen Regionen markieren Bereiche, von denen aus die jeweilige Stachelspitze sichtbar ist. Da sich keine zwei dieser Bereiche überschneiden, braucht man also hier mindestens n/3 Standorte, um alle Stacheln zu überblicken. Das Zerlegen von Polygonen in einfachere Teile, insbesondere das Triangulieren, ist für viele Anwendungen wichtig, und so werden wir uns unter anderem mit effizienten Algorithmen hierfür beschäftigen. 3. Beispiel: Wirkungsbereiche In eine große Schale mit Nährlösung werden einige Bakterienkulturen gegeben. Wir stellen uns vor, dass diese sich gleichmäßig und alle mit der gleichen Geschwindigkeit ausbreiten. Zunächst wären dann alle Kulturen kreisförmig so wie hier im Bild: 8 1. ALGORITHMISCHE GEOMETRIE - WAS IST DAS? Nach einer Weile aber beginnen sich Paare von Kulturen zu berühren und können sich dann in die jeweiligen Richtungen nicht mehr weiter ausbreiten. Die gemeinsame Grenze eines solchen Paares verläuft entlang der Mittelsenkrechte zwischen ihren jeweiligen Zentren: Irgendwann schließlich ist der gesamte freie Raum verbraucht und es ergibt sich ein Bild wie das folgende: Jeder Punkt der Schale ist nun von der Kultur bedeckt, deren Zentrum ihm am nächsten liegt. Die nach diesem Prinzip entstehenden 3. BEISPIEL: WIRKUNGSBEREICHE 9 Gebietszerlegungen haben viele Namen, welche die sehr unterschiedlichen Bereiche widerspiegeln, in denen sie von Nutzen sind. In der Physik zum Beispiel gibt es die Wigner-Seitz-Zellen, während man in der Kristallographie von Wirkungsbereichen spricht. In der Mathematik und Informatik heißen solche Zerlegungen Voronoi-Diagramme, und die einzelnen Gebiete Voronoi-Zellen. Voronoi-Diagramme haben ein Vielzahl interessanter Eigenschaften. Zum Beispiel sind Voronoi-Zellen immer konvex. Dies ermöglicht, wie wir sehen werden, effiziente Verfahren, die zu einem beliebigen Punkt seine Zelle und damit das ihm am nächsten liegende Zentrum bestimmen. An den Ecken eines Diagramms, also jenen Punkten, an denen mindestens drei Zellen zusammenstoßen, hat die Funktion, die jedem Punkt den Abstand zum nächstliegenden Zentrum zuordnet, jeweils ein lokales Maximum. Stellen wir uns die Zentren einmal nicht mehr als Startpunkte von Bakterienkulturen, sondern zum Beispiel als Standorte von Einkaufszentren vor, so könnte man dies benutzen, um für ein geplantes neues Einkaufszentrum den günstigsten Bauplatz zu finden. Es gibt noch eine ganze Reihe weiterer wichtiger Eigenschaften und Anwendungen von Voronoi-Diagrammen und den mit ihnen eng verwandten Delone-Zerlegungen, weswegen wir diese beiden Konstruktionen eingehend studieren und effiziente Algorithmen zu ihrer Berechnung kennenlernen werden. KAPITEL 2 Schnittpunktprobleme Wir betrachten jetzt das Problem, möglichst schnell alle Schnittpunkte einer (eventuell sehr großen) Anzahl n von geometrischen Objekten zu berechnen. Man kann hier sehr schön einige wichtige Prinzipien geometrischer Algorithmen sehen. Ein naiver Algorithmus würde jedes Objekt mit jedem anderen vergleichen und damit eine quadratische Anzahl von einzelnen Schnittberechnungen benötigen. In den meisten Anwendungen können tatsächlich quadratisch viele Schnittpunkte vorkommen, weswegen das naive Verfahren schon worst-case optimal ist. Häufig aber ist die tatsächliche Anzahl von Schnitten viel kleiner, und es ist dann interessant, Verfahren zu finden, die dies ausnutzen. Beim Entwurf von geometrischen Algorithmen kann man häufig eines der drei folgenden Prinzipien einsetzen: (1) Inkrementelle Konstruktion: man fügt immer ein Objekt nach dem anderen hinzu und nutzt dabei zuvor gewonnene Information aus. (2) Teile und Herrsche: man zerlegt das Problem in zwei etwa gleich große Teile, löst diese und fügt dann die Einzellösungen zusammen. Solange die Teilprobleme noch eine gewisse Größe überschreiten, wird das Verfahren rekursiv durchgeführt. (3) Plane-Sweep: man reduziert ein d-dimensionales Problem auf eine Abfolge von (d−1)-dimensionalen, indem man jeweils den Schnitt der geometrischen Situation mit einer (d − 1)-dimensionalen Hyperebene, also für den zweidimensionalen Fall zum Beispiel einer Geraden betrachtet. Diese gedachte Hyperebene wird im Laufe des Verfahrens über die gesamte zu untersuchende Konfiguration bewegt. Die ersten zwei Prinzipien sind in der Algorithmenentwicklung praktisch universell einsetzbar: so ist zum Beispie Insertion Sort ein typische inkrementelles Sortierverfahren, während Quicksort nach dem Prinzip Teile und Herrsche“ funktioniert. Plane-Sweep-Algorithmen ” hingegen sind eher typisch für geometrische Probleme oder zumindest solche, die sich in gewisser Weise geometrisch interpretieren lassen. 11 12 2. SCHNITTPUNKTPROBLEME In diesem Kapitel wird aus Zeitgründen nur ein von Bentley und Ottmann stammendes Plane-Sweep-Verfahren für die Berechnung von Streckenschnittpunkten vorgestellt. Dieses ist zwar nicht laufzeitoptimal, aber dafür relativ einfach zu beschreiben. Gegeben sei eine Menge von n Strecken, S = {s1 , . . . , sn }, wobei die Strecke si jeweils durch ihren Anfangspunkt ai mit den Koordinaten xa,i und ya,i und ihren Endpunkt ei mit den Koordinaten xe,i und ye,i angegeben wird. 1. Schnittpunkte achsenparalleler Strecken Zur Vereinfachung nehmen wir zunächst an, dass alle Strecken entweder senkrecht oder waagerecht sind, und dass ansonsten alle auftretenden x- und y-Koordinaten nur einmal vorkommen. Für eine horizontale Strecke si gilt also ya,i = ye,i , und entsprechend für eine vertikale xa,i = xe,i . Wir nehmen weiter an, dass für horizontale Strecken xa,i < xe,i und für vertikale Strecken ya,i < ye,i gilt, also alle Strecken von links nach rechts beziehungsweise — ein rechtshändiges Koordinatensystem vorausgesetzt — von unten nach oben verlaufen. Die Idee beim Plane-Sweep-Verfahren ist es nun, die horizontalen Strecken in einer geeigneten Datenstruktur so zu verwalten, dass für jeden möglichen x-Wert die ihn überlappenden Strecken leicht abgelesen werden können. Wir können uns den zu entwerfenden Algorithmus als einen dynamischen Prozess vorstellen, in dem eine senkrechte Referenzgerade von links nach rechts über die Ebene bewegt wird. Für die meisten x-Werte ändert sich die Konfiguration, die diese Gerade sieht“, nicht. Nur wenn horizontale Strecken beginnen oder enden, ” muss der Inhalt der Datenstruktur angepasst werden. 1. SCHNITTPUNKTE ACHSENPARALLELER STRECKEN 13 An jeder x-Position einer vertikalen Strecke müssen ausserdem aus den gerade aktiven, dass heisst die Referenzgerade schneidenden, horizontalen Strecken diejenigen herausgefischt werden, die diese schneiden. Es gibt also insgesamt drei Arten von Ereignissen, was die folgende Beschreibung des Algorithmus wiederspiegelt: Algorithmus 1. Schnittpunkte achsenparalleler Strecken. Eingabe: Ebene Strecken si = [(xa,i , ya,i ), (xe,i , ye,i )] für i = 1, . . . , n, mit entweder xa,i = xe,i und ya,i < ye,i oder ya,i = ye,i und xa,i < xe,i . Alle vorkommenden x- und y-Koordinaten seien verschieden. Ausgabe: Alle Schnittpunkte zwischen den eingegebenen Strecken. 1. Sortiere alle vorkommenden x-Koordinaten in ein Array A. 2. Erzeuge eine leere Datenstruktur B für horizontale Strecken. 3. Für jeden Eintrag x in A: 4. Falls x = xa,i für eine horizontale Strecke si : 5. Füge si in B ein. 6. Sonst, falls x = xe,i für eine horizontale Strecke si : 7. Lösche si aus B. 8. Sonst, falls x = xa,i = xe,i für eine vertikale Strecke si : 14 9. 2. SCHNITTPUNKTPROBLEME Gib von den in B eingetragen Strecken alle die aus, deren y-Wert zwischen ya,i und ye,i liegt. Das Vorsortieren in Zeile 1 ist offenbar in Laufzeit O(n log n) machbar, während der Schleifenrumpf in den Zeilen 4 bis 9 höchstens O(n)mal durchlaufen wird. Es bleibt, eine geeignete Datenstruktur zu finden, welche das Einfügen und Löschen von Zahlen, nämlich den yWerten horizontaler Strecken, und außerdem das Auffinden aller Zahlen in einem bestimmten Intervall jeweils möglichst effizient erlaubt. Das geht zum Beispiel mit balancierten binären Suchbäumen. Das Einfügen und Löschen braucht dann jeweils O(n) Schritte, und das Auffinden aller Werte in einem gegebenen Intervall geht in Laufzeit O(log n + k), wobei k die Anzahl der gefundenen Ergebnisse ist. Da die Summe aller auftretenden Werte für k gerade die Gesamtanzahl I aller gefundenen Schnittpunkte ist, ergibt sich für Algorithmus 1 eine Gesamtlaufzeit von O(n log n + I). Der Speicherbedarf für die Strukturen A und B ist offenbar jeweils O(n), womit auch der Gesamtspeicherbedarf des Verfahrens linear wird. Es ist nun nicht allzu schwer, die Einschränkung, dass alle x-Werte nur einmal auftreten, aufzuheben. Da im folgenden Abschnitt gleich wesentlich stärker verallgemeinert wird, sparen wir uns diese Überlegung aber hier. 2. Allgemeiner Streckenschnitt Wenn wir denselben Ansatz wie oben beim allgemeinen Streckenschnittproblem anwenden wollen, so erkennen wir bald, dass sich auch die relative Lage von Strecken im Bezug auf die vertikale Referenzgerade ändern kann. Das passiert immer dann, wenn zwei Strecken einen inneren Schnittpunkt haben, also einen, der nicht gleichzeitig Endpunkt einer dieser Strecken ist. In diesem Fall nämlich tauschen sie entlang der Referenzgeraden gerade ihre Plätze. 2. ALLGEMEINER STRECKENSCHNITT 15 Diese neue Art von Ereignis kann erst im Laufe des Algorithmus ermittelt werden. Wir benötigen daher an Stelle des Arrays A nun eine dynamische Datenstruktur, und zwar, um jeweils das anstehende Ereignis mit der kleinsten x-Koordinate aufzufinden, am günstigsten eine Prioritätswarteschlange. Da wir, wie sich später noch genauer zeigen wird, nicht sicherstellen können, dass wir jedes Ereignis nur einmal berechnen, implementieren wir diese nicht wie üblich durch einen Heap, sondern wieder als binären Suchbaum. Man könnte zumächst auf die Idee kommen, immer dann, wenn eine neue Strecke hinzukommt, diese mit allen gerade aktiven Strecken zu schneiden. Dieses Verfahren ist aber, wie man leicht sieht, nicht ausgabesensitiv. Man betrachte beispielsweise eine Konfiguration wie diese hier: Man würde am Ende doch wieder jede Strecke mit jeder anderen schneiden, müsste also insgesamt quadratisch viele Schnitte berechnen, obwohl die tatsächliche Schnittanzahl 0 ist. Günstiger ist es, nach jedem Ereignis nur diejenigen Paare von Strecken zu untersuchen, die durch das Ereignis zu Nachbarn geworden sind. Schneiden sich nämlich bei einem gewissen x-Wert zwei oder mehr Strecken, so müssen mindestens zwei davon, sagen wir si und sj , kurz zuvor benachbart gewesen sein. Da vor dem allersten Ereignis weder si noch sj aktiv sind, muss es ein Ereignis gegeben haben, bei dem sie Nachbarn wurden. 16 2. SCHNITTPUNKTPROBLEME Dies zeigt, dass das Untersuchen von neuen Nachbarn nach jedem Ereignis ausreicht, um jeden Schnittpunkt wenigstens einmal zu finden. Wir müssen noch zwei Spezialfälle berücksichtigen: zum einen könnten vertikale Strecken, oder allgemeiner mehrere Ereignisse mit derselben x-Koordinate, vorkommen. Um diese zu unterscheiden, kann als zweites Prioritätskriterium die y-Koordinate benutzt werden. Das funktioniert, weil man sich vorstellen kann, die Referenzgerade um ein Winzigkeit nach links zu kippen, so dass tiefere Punkte bei gleicher x-Koordinate etwas früher erreicht werden. Wir können auch sagen: wir definieren eine vollständige Ordnungsrelation auf der Menge aller Punkte der Ebene, indem wir für zwei Punkte p1 = (x1 , y1 ) und p2 = (x2 , y2 ) den ersten als kleiner festlegen und dann p1 < p2 schreiben, wenn entweder x1 < x2 oder aber x1 = x2 und y1 < y2 gilt. Ein Ereignis an der Stelle p1 soll in diesem Fall immer vor einem Ereignis an der Stelle p2 behandelt werden. Eine sinnvolle Konvention ist es dann auch, für eine Strecke si mit Anfangspunkt ai und Endpunkt ei festzulegen, dass immer ai < ei gelten soll, so dass der notierte Anfangspunkt mit dem Anfangsereignis für diese Strecke übereinstimmt. 2. ALLGEMEINER STRECKENSCHNITT 17 Desweiteren könnten am selben Punkt der Ebene mehrer Strecken beginnen, enden oder sich schneiden. Eine mögliche Vorgehensweise, die diesen Fall mit berücksichtigt, ist es, jeweils nur das Koordinatenpaar, an dem ein Ereignisses eintritt, in der Ereigniswarteschlange abzuspeichern, und zwar auch nur dann, wenn es nicht schon dort vorhanden ist. Beim Abarbeiten werden dementsprechend zunächst alle Strecken gesucht, die am jeweiligen Ereignispunkt beginnen, enden oder diesen enthalten. Hier ist nun zunächst die grobe Struktur des Algorithmus: Algorithmus 2. Schnittpunkte beliebiger Strecken. Eingabe: Ebene Strecken si = [ai , ei ] für i = 1, . . . , n, wobei jeweils ai < ei gilt. Ausgabe: Alle Schnittpunkte zwischen den eingegebenen Strecken, und für jeden dieser Schnittpunkte die Liste aller ihn schneidenden Strecken. 1. Erzeuge eine leere Ereigniswarteschlange Q und füge dort alle Streckenendpunkte ein. Jedes vorkommende Koordinatenpaar wird nur einmal gespeichert, erhält aber eine Liste aller dort beginnenden Strecken. 2. Erzeuge eine leere Statusstruktur B zur Aufnahme der jeweils aktiven Strecken. 3. Solange Q nicht leer ist: 4. Nimm den nächsten Ereignispunkt p zusammen mit der Liste A(p) der in p beginnenden Strecken aus Q heraus. 5. VerarbeiteEreignis(p, A(p)) Die Prozedur VerarbeiteEreignis ist dafür zuständig, die in p endenden Strecken aus B zu entfernen, die dort beginnenden einzufügen, für sich dort schneidende Strecken die neue Reihenfolge zu bestimmen und schließlich eventuelle neue Schnittpunkte zu finden und in Q einzutragen. VerarbeiteEreignis(p, A(p)) 1. Finde alle in B eingetragenen Strecken, die p enthalten. 2. Bezeichne die Menge der in p endenden Strecken mit E(p). 3. Bezeichne die Menge der p in ihrem Innern enthaltenden Strecken mit I(p). 4. Falls A(p) ∪ I(p) ∪ E(p) mindestens zwei Element enthält: 5. Gib den Schnittpunkt p zusammen mit allen Strecken aus A(p) ∪ I(p) ∪ E(p) aus. 18 2. SCHNITTPUNKTPROBLEME 6. Entferne die in I(p) ∪ E(p) enthaltenen Strecken aus B. 7. Füge die in A(p) ∪ I(p) enthaltenen Strecken nach ihrer Steigung sortiert in B ein, wobei vertikale Strecken zuletzt kommen. 8. /∗ Das Löschen und Wiedereinfügen der Strecken aus I(p) kehrt automatisch deren Reihenfolge um. ∗/ 9. Falls in p nur Strecken enden (A(p) ∪ I(p) = ∅): 10. Finde in B die jeweils direkt über und direkt unter p liegende Strecke su und so . 11. FindeSchnittpunkt(su , so , p) 12. Sonst: 13. Finde in A(p) ∪ I(p) die Strecke st mit der kleinsten Steigung und in B deren unteren Nachbarn su . 14. FindeSchnittpunkt(su , st , p) 15. Finde in A(p) ∪ I(p) die Strecke sh mit der größten Steigung und in B deren oberen Nachbarn so . 16. FindeSchnittpunkt(sh , so , p) Hier ist noch zu beachten, dass die in den Zeilen 9 bis 16 benutzten Strecken su und so nicht immer existieren. In diesem Fall ist natürlich nichts zu tun. Es fehlt nun noch die Prozedur, die einen neuen Schnittpunkt sucht. Es werden nur Schnittpunkte q mit p < q berücksichtigt, da alle übrigen bereits behandelt wurden. FindeSchnittpunkt(su , so , p) 1. Falls die Strecken su und so einen Schnittpunkt q mit p < q besitzen und dieser noch nicht in Q eingetragen ist: 2. Trage q in Q ein . Offenbar produziert Algorithmus 2 nur tatsächliche Schnittpunkte. Es bleibt zu beweisen, dass der Algorithmus keinen Schnittpunkt übersieht. Lemma 1. Algorithmus 2 findet bei Eingabe einer Liste s1 , . . . , sn ebener Strecken alle Schnittpunkte. Beweis. Wir nehmen an, dass p ein Schnittpunkt ist und dass der Algorithmus alle Schnittpunkte vor p findet und korrekt verarbeitet. Wenn wir zeigen können, dass dann auch p mit allen sich dort schneidenden Strecken gefunden wird, folgt die Korrektheit des Algorithmus per Induktion. 2. ALLGEMEINER STRECKENSCHNITT 19 Falls p Endpunkt einer der Strecken s1 bis sn ist, wurde p in Schritt 1 in die Ereigniswarteschlange Q eingetragen. Die Liste A(p) der in p beginnenden Strecken wurde zusammen mit p gespeichert. Alle übrigen Strecken, die p enthalten, müssen bereits in B eingetragen sein, wenn p durch die Prozedur VerarbeiteEreignis behandelt wird. Sie werden daher in Schritt 1 der Prozedur gefunden. Falls p nicht Endpunkt einer Strecke ist, sortiere man die p enthaltenden Strecken nach ihrer Steigung und betrachte zwei in der sortierten Liste benachbarte Strecken si und sj . Falls keine dieser beiden Strecken vertikal ist, so müssen sie auch bezüglich einer genügend nahe links von p liegenden Referenzgeraden benachbart sein. Da alle Schnittpunkte vor p korrekt behandeln wurden, müssen dann si und sj auch Nachbarn in B sein. Ist andernfalls zum Beispiel si vertikal, so muss sj unter allen p enthaltenden Strecken die größte Steigung haben. Dann müssen spätestens nach der Abarbeitung des letzten direkt unter p liegenden Ereignispunktes si und sj wiederum benachbart sein. In beiden Fällen muss es, da B ganz zu Anfang leer war, ein Ereignis vor p geben, das si und sj zu Nachbarn macht, und bei dessen Abarbeitung also der Schnittpunkt p in Q eingetragen wurde. Wir untersuchen nun noch das Laufzeitverhalten von Algorithmus 2. Lemma 2. Die Laufzeit von Algorithmus 2 für eine Liste von n Strecken ist O(n log n + k log n + (n + I) log(n + I)), wobei k die Summe der Anzahlen der jeweils einen Ereignispunkt enthaltenden Strecken und I die Anzahl der Schnittpunkte ist. Beweis. Das Einfügen, Löschen oder Auffinden eines Eintrages in einem balancierten binären Suchbaum mit m Einträgen benötigt jeweils eine Laufzeit von O(log m). Dies gilt dementsprechend auch für die Strukturen B und Q. Der Aufbau der Ereigniswarteschlange Q in Schritt 1 des Algorithmus geht also in Laufzeit O(n log n). Schritt 2 benötigt dagegen nur konstante Zeit. Der Algorithmus verarbeitet O(n + I) Ereignisse. Bei jedem Aufruf von VerarbeiteEreignis müssen die p enthaltenden Strecken in B gefunden, dann einige dieser Strecken aus B gelöscht und einige in B eingefügt werden. Da B zu jedem Zeitpunkt maximal n Einträge hat, benötigt jede dieser Operationen O(log n) Schritte. Dies ergibt für alle Ereignisse zusammengerechnet eine Laufzeit von O(k log n). 20 2. SCHNITTPUNKTPROBLEME Schließlich muss jeder Ereignispunkt aus Q entfernt und in der Prozedur FindeSchnittpunkt bis zu zwei neue Ereignispunkte eingetragen werden. Da Q zu jedem Zeitpunkt maximal O(n + I) Einträge enthalten kann, geht das insgesamt in der Zeit O((n+I) log(n+I)). Der Algorithmus hat also tatsächlich ein ausgabesensitives Laufzeitverhalten. Der Speicherplatzbedarf ist durch die Größe der Struktur Q, nämlich O(n+I) beschränkt. Man kann dies allerdings durch einen kleinen Trick auf O(n) verbessern. Werden nämlich zwei sich schneidende Strecken im Laufe des Algorithmus mehrmals zu Nachbarn, so bleibt ihr Schnittpunkt vom ersten dieser Ereignisse an in Q gespeichert. Dadurch kann Q unter Umständen sehr groß werden. Wenn man den Schnittpunkt zweier Strecken aus Q entfernt, sobald diese aufhören, Nachbarn zu sein, bleibt die Größe von Q immer linear und man erhält insgesamt einen Speicherbedarf von O(n). Gleichzeitig reduziert sich der Laufzeitterm O((n + I) log(n + I) aus Lemma 2 auf O((n + I) log n). Es lässt sich weiter sogar zeigen, dass der Wert k linear in I ist, was die Laufzeit insgesamt auf O((n + I) log n reduziert. Dafür benötigen wir aber ein kleines graphentheoretisches Argument. Wir betrachten nämlich nun unsere Konfiguration von Strecken als einen planaren Graphen, dessen Knoten sowohl die End- als auch die Schnittpunkte der Strecken und dessen Kanten die jeweils zwischen zwei Knoten gelegenen Abschnitte der ursprünglichen Strecken sind. 2. ALLGEMEINER STRECKENSCHNITT 21 Kurz zur Auffrischung der Terminologie: ein (ungerichteter) Graph besteht aus einer Menge von Knoten sowie einer Menge von ungeordneten Paaren von Knoten, die Kanten genannt werden. Eine Kante repräsentiert eine Verbindung zwischen zwei Knoten. Ein Graph heisst zusammenhängend , wenn es zwischen je zwei Knoten einen Verbindungsweg entlang der Knoten und Kanten des Graphen gibt. Ein Graph heißt planar , wenn er ohne Überschneidungen in die Ebene gezeichnet werden kann, wobei Knoten jeweils durch Punkte und Kanten durch Strecken oder Kurven zwischen diesen Punkten dargestellt werden. Derselbe Graph kann möglicherweise auf mehrere grundsätzlich verschiedene Weisen gezeichnet werden, weswegen man strenggenommen zwischen dem Graphen selbst und seiner Darstellung in der Ebene, der sogenannten Einbettung unterscheiden muss. In der algorithmischen Geometrie beschäftigen wir uns aber meist mit einer schon vorgegebenen Einbettung. Wir sprechen dann von einem eingebetteten planaren Graphen und meinen im Folgenden, wenn von planaren Graphen die Rede ist, immer solche. Ein planarer Graph zerlegt die Ebene in zusammenhängende Gebiete, die Flächen genannt werden. Wir betrachten nur Graphen mit endlicher Knotenmenge. In diesem Fall gibt es genau eine unbeschränkte ( unendliche“) Fläche, die wir auch als äussere Fläche bezeichnen. ” Alle anderen Flächen sind beschränkt und heissen auch innere Flächen. Bezeichnen wir mit V , E und F die Anzahlen der Knoten, Kanten und Flächen eines planaren zusammenhängenden Graphen, so gilt die von 22 2. SCHNITTPUNKTPROBLEME Euler entdeckte Beziehung V − E + F = 2, die man relativ leicht per Induktion über die Kantenanzahl E beweisen kann. Einen nichtzusammenhängenden planaren Graphen kann man leicht durch Hinzufügen von Kanten zusammenhängend machen. Es gilt daher für beliebige planare Graphen allgemeiner V − E + F ≥ 2. Die in Lemma 2 angesprochene Zahl k, nämlich die Summe der jeweils einen Ereignispunkt enthaltenden Strecken, lässt sich jetzt nach oben hin durch 2E abschätzen. Man zählt nämlich schlimmstenfalls jede Kante des Graphen von jedem ihrer Endpunkte aus einmal. Sofern insgesamt mindestens 3 Kanten vorkommen, muss auch jede Fläche von mindestens 3 Kanten begrenzt sein. Andererseits kann jede Kante höchstens zwei Flächen begrenzen. Damit ergibt sich 3F ≤ 2E, und durch Einsetzen in die Euler-Formel oben weiterhin 3V − 3E + 2E ≥ 6 beziehungsweise E ≤ 3V − 6. Wir erhalten also insgesamt k ≤ 2E ≤ 6V − 12 = 12n + 6I − 12 = O(n + I). Der folgende Satz fasst die Ergebnisse dieses Abschnittes zusammen. Satz 1. Alle Schnittpunkte einer Menge von n Strecken in der Ebene sowie die Listen der in jeden Schnittpunkt involvierten Strecken lassen sich in Laufzeit O((n + I) log n) und Speicherplatz O(n) berechnen, wobei I die Anzahl der Schnittpunkte ist. 3. Eine Datenstruktur für Karten Eine mögliche Anwendung des Algorithmus aus dem vorigen Abschnitt ist es, Überlagerungen von Karten oder Plänen zu berechnen. Dabei macht es in der Regel wenig Sinn, eine Karte nur als Ansammlung von Strecken zu repräsentieren. Für eine Straßenkarte zum Beispiel ist es wahrscheinlich vernünftig, das abgebidete Straßennetz als eingebetteten Graphen zu repräsentieren, damit zum Beispiel RoutenplanerProgramme effizient nach Wegen suchen können. Für andere Zwecke, wie zum Beispiel die Darstellung der durchschnittlichen jährlichen Regenmengen in einer bestimmten Region, braucht man neben den Grenzen der einzelnen Gebiete auch eine Repräsentation der Gebiete selbst, 3. EINE DATENSTRUKTUR FÜR KARTEN 23 um Informationen zu diesen speichern zu können. Ähnlich wie bei Graphen möchte man dann zum Beispiel Nachbarschaftsbeziehungen zwischen Gebieten direkt ablesen können. Andererseits soll eine Datenstruktur, die eine einfache Repräsentation einer Karte implementiert, nicht unnötig kompliziert sein und noch keine Funktionalität unterstützen, die aufwendige Algorithmen und Datenstrukturen erfordert. Für unsere Zwecke ist eine Karte einfach ein (eingebetteter) planarer Graph mit zusätzlichen Flächeninformationen. Sie enthält also Knoten, Kanten und Flächen. Für jeden Knoten ist eine Position, dass heisst ein Punkt der Ebene, auf dem dieser Knoten liegt, zu speichern. Außerdem können sowohl Knoten als auch Kanten und Flächen Zusatzdaten enthalten, die von der jeweiligen Anwendung abhängen. Die einzelnen Bestandteile einer Karte müssen außerdem noch auf geeignete Weise verknüpft werden. Kante Fläche Knoten Die im Folgenden vorgestellte Datenstruktur ist als doppelt verknüpfte Kantenliste oder auch Halbkantenstruktur bekannt. Es gibt eine Reihe von Varianten unter verschiedenen Namen, die alle nach ungefähr dem gleichen Prinzip funktionieren. Sie implementiere alle im Wesentlichen eine mathematische Struktur, die sich kombinatorische Karte oder einfach Karte nennt. Der wichtigste Aspekt einer Halbkantenstruktur ist die Repräsentation der Kanten und deren Verknüpfungen. Tatsächlich wird jede Kante durch zwei Datenobjekte dargestellt, nämlich ihre beiden gerichteten Versionen, dargestellt. Eine Kante zwischen zwei Knoten a und b kann ja entweder als Kante von a nach b oder als Kante von b nach a aufgefasst werden. Diese zwei gerichteten Versionen werden auch manchmal als Halbkanten bezeichnet. Jede Halbkante enthält einen Verweis auf ihren entgegengesetzten Partner und auf ihren Anfangsknoten. Der Endknoten ist ja der Anfangsknoten des Partners und muss nicht noch 24 2. SCHNITTPUNKTPROBLEME einmal gespeichert werden. Desweiteren enthält jede Halbkante einen Verweis auf die links von ihr liegende Fläche. Die rechts liegende kann wiederum aus den Daten des Partners rekonstruiert werden. Um die zu einer Fläche gehörende Kantenfolge rekonstruieren zu können, bekommt jede Halbkante auch einen Verweis auf die unmittelbar folgende und unmittelbar vorhergehende Halbkante im Rand der links von ihr gelegenen Fläche. e.start e.reverse e e.next e.prev e.leftFace Hier ist die Repräsentation einer Halbkante in vereinfachter JavaSyntax: class HalfEdge { Node start; Face leftFace; HalfEdge reverse; HalfEdge next; HalfEdge prev; } Wie erwähnt, lassen sich verschiedene weitere lokale Beziehungen leicht ablesen: Node end(Halfedge e) { return e.reverse.start; } Face rightFace(HalfEdge e) { return e.reverse.leftFace; } 3. EINE DATENSTRUKTUR FÜR KARTEN 25 Die Funktionen clockwise und counterclockwise liefern jeweils eine Halbkante mit dem gleichen Anfangsknoten wie die Eingabekante, wobei clockwise die im Uhrzeigersinn und counterclockwise die entgegen dem Uhrzeigersinne nächste dieser Halbkanten ermittelt. HalfEdge clockwise(HalfEdge e) { return e.reverse.next; } HalfEdge counterclockwise(HalfEdge e) { return e.prev.reverse; } Für jeden Knoten muss eine Position und eine in diesem Knoten beginnende Halbkante gespeichert werden. Man kann dann zum Beispiel mit Hilfe der Funktion clockwise leicht alle von diesem Knoten ausgehenden Halbkanten auffinden. Die vereinfachte Java-Klasse für Knoten sieht dann so aus: class Node { double posX; double posY; HalfEdge startingHere; } Bei der Beschreibung der Flächen kann es eine kleine Komplikation geben. Ist nämlich der zugrundeliegende Graph unzusammenhängend, so gibt es auch Flächen, deren Rand aus mehreren Komponenten besteht. In diesem Fall müsste die Flächenrepräsentation Verweise auf jeweils eine (Halb-)Kante in jeder dieser Komponenten enthalten. Dies lässt sich vermeiden, indem man zusätzliche Dummy“-Kanten ” einfügt, um den Graphen zusammenhängend zu machen. Diese müssen dann natürlich als unsichtbar gekennzeichnet werden. Dieses Vorgehen hat auch den Vorteil, dass von einem beliebigen Knoten aus alle Knoten, Kanten und Flächen durch eine einfache Tiefen- beziehungsweise Breitensuche besucht werden kann. 26 2. SCHNITTPUNKTPROBLEME Die auf zusammenhängende Graphen spezialisierte Flächenklasse sieht dann einfach so aus: class Face { HalfEdge bounding; } Natürlich können sämtliche Klassen je nach Anwendung noch weitere Komponenten enthalten. Für zwei zusammengehörende Halbkanten empfiehlt sich, um keine Daten doppelt abspeichern zu müssen, dann die Einführung einer weiteren Klasse EdgeInfo und die Aufnahme einer entsprechenden Komponente in die HalfEdge-Repräsentation. Wir beschäftigen uns nun mit Erweiterungen des Algorithmus von Bentley und Ottmann auf Daten, die durch eine oder mehrere Halbkanten-Datenstrukturen repräsentiert sind. Ein Beispiel ist das Überlagern zweier Zerlegungen, wie hier im Bild angedeutet. Dabei entstehen die Flächen der neuen Zerlegung gerade als die nichtleeren Schnitte von jeweils einer Fläche aus jeder der ursprünglichen Zerlegungen. Die Knoten sind entweder Knoten einer der ursprünglichen Zerlegungen oder Schnittpunkte und die Kanten sind die durch eventuelle Schnittpunktknoten unterteilten alten Kanten. Übrigens haben wir weder bei der Definition von Karten noch bei der Einführung der Halkantenstruktur gefordert, dass Kanten als Strecken realisiert sind. Möglich wären ja zum Beispiel auch Kurvenstücke. In diesem Fall müsste natürlich jede Kante zusätzliche Informationen über ihre Realisierung tragen. Ab jetzt wollen wir allerdings immer annehmen, dass alle Kanten als Strecken realisiert sind und dementsprechend kompliziertere Randteile zwischen Flächen als Streckenzüge mit eingebetteten Knoten dargestellt werden. Man könnte sich aber auch vorstellen, ausgehend von einer ungeordneten Menge von Strecken, wie wir sie weiter oben immer betrachtet haben, eine Halbkantenstruktur zu berechnen, welche die von 3. EINE DATENSTRUKTUR FÜR KARTEN 27 diesen Strecken gebildete Zerlegung repräsentiert. Zu jeder der Eingabestrecken wird dann zunächst eine triviale Halbkantenstruktur generiert, die aus zwei gepaarten Halbkanten, zwei Knoten und einer Fläche, nämlich der äußeren, unendlichen“, besteht. ” Die Vorgehensweise ist in etwa die folgende: zunächst fasst man sämtliche gegebenen Halbkantenstrukturen zu einer einzigen zusammen, die vorerst noch keine gültige Zerlegung repräsentiert. Man sammelt dann sämtliche vorkommenden Kanten, beziehungsweise die durch sie definierten Strecken, und legt zwischen diesen und den sie definierenden Halbkanten Querverweise in beide Richtungen an. Im Laufe des Algorithmus dienen diese Verweise dazu, für die jeweils gefundenen Schnittpunkte auch die beteiligten Komponenten der Halbkantenstrukturen schnell ermitteln zu können. Man führt nun zunächst einfach Algorithmus 2 aus, ersetzt aber Schritt 5 der Prozedur VerarbeiteEreignis durch eine Berechnung, welche die Halbkantenstruktur lokal an dieser Stelle korrigiert. Dies führt dazu, dass am Ende alle Halbkanten- und Knoten-Informationen für die überlagerte Zerlegung stimmen. In einer abschließenden Phase werden dann auch die Flächeninformationen angepasst. Bei der lokalen Anpassung der Halbkantenstruktur ist zu beachten, dass ein Schnittpunkt für manche Strecken ein Endpunkt und gleichzeitig für andere ein innerer Punkt sein kann. Zur Vereinheitlichung ist es sinnvoll, zunächst diejenigen Kanten, die den gerade behandelten Schnittpunkt in ihrem Inneren haben, an dieser Stelle zu unterteilen. In der Halbkantenstruktur ist dort also eine zusätzliche Knoten einzufügen und außerdem sind aus einem Paar von Halbkanten zwei aufeinander folgende Paare zu machen. Nach dieser Normalisierung müssen noch die an diesem Punkt auftretenden Knoten zu einem einzigen zu vereinigt und die next und prev-Einträge der beteiligten Halbkanten aktualisiert werden. 28 2. SCHNITTPUNKTPROBLEME Dazu muss im Prinzip nur die zyklische Reihenfolge der vom neuen vereinigten Knoten ausgehenden Kanten bekannt sein. Dies geht mit einem einfachen Sortieren nach dem Winkel in der Zeit O(d log d), wobei d der Grad des Knotens ist. Da das oben angedeutete Aufspalten einer Kante in konstanter Zeit geht, erhöht sich also durch die lokale Modifikation der Halbkantenstruktur die Laufzeit asymptotisch nicht. Um die passenden Flächeninformationen für die Überlagerungskarte zu bestimmen, genügt im Wesentlichen eine Traversierung der entstandenen Halbkantenstruktur. Dabei werden die durch die nextbeziehungsweise prev-Einträge induzierten Zyklen in der Halbkantenstruktur verfolgt und neuen Flächen-Instanzen zugeordnet. Dabei gibt es eventuell zwei kleinere Schwierigkeiten. Zum einen muss nämlich die neu entstandene Halbkantenstruktur nicht notwendigerweise zusammenhängend sein, und zwar auch dann nicht, wenn sie aus lauter zusammenhängenden Strukturen gebildet wurde. Es können also insbesondere Flächen auftauchen, deren Rand aus mehreren Zyklen besteht. 3. EINE DATENSTRUKTUR FÜR KARTEN 29 Einen Ansatz zur Lösung dieses Problem erhalten wir durch eine kleine Erweiterung zu Algorithmus 2. Für jeden Ereignispunkt, beziehungsweise den zu diesem gehörenden Knoten, wird ein Verweis auf die direkt darunter liegende Strecke gespeichert, sofern es eine solche gibt. Stellt sich der zur neuberechneten Karte gehörende Graph später als unzusammenhängend heraus, so kann auf diese Weise für jede Komponente festgestellt werden, in welcher Fläche welcher anderen Komponente sie liegt. Dazu folgt man einfach dem im tiefsten Knoten dieser Komponente gespeicherten Verweis. Dieser zeigt entweder auf eine andere in derselben Fläche liegende Komponente oder aber auf den äusseren Rand dieser Fläche. Da alle diese Verweise nach unten zeigen, wird nach endlich vielen Schritten immer der Verweis auf den Flächenrand beziehungsweise ein leerer Verweis gefunden. Im letzteren Fall liegt die ursprüngliche Komponente natürlich ganz aussen“. ” Falls die Flächen der überlagerten Karten Zusatzinformationen enthalten, so stellt sich noch die Frage, wie man die entsprechende Information für die Überlagerungskarte gewinnt. In einer Überlagerung einer politischen mit einer Vegetationskarte würde beispielsweise der Schnitt der Fläche Norwegen“ mit einer Fläche Wald“ eine Fläche ” ” norwegischer Wald“ ergeben. Falls eine solche Fläche Kanten aus bei” den Ursprungskarten enthält, lässt sich diese Information ganz einfach 30 2. SCHNITTPUNKTPROBLEME durch Verfolgen der Zyklen gewinnen. Es könnte aber auch eine Fläche der einen Karte ganz in einer der anderen liegen. In diesem Fall ist zusätzliche Arbeit erforderlich. Mögliche Ansätze sind wiederum eine neue Erweiterung des Plane-Sweep Algorithmus oder eine Nachbearbeitung der Überlagerungskarte, bei der die Flächen in einer geeigneten Reihenfolge besucht und dabei bekannte Informationen über Flächengrenzen hinweg kopiert werden. KAPITEL 3 Triangulierung von Polygonen 1. Das klassische Museumsproblem Die einfachste Form des Museumsproblems und seine Beziehung zu Polygontriangulierungen haben wir schon im Einführungskapitel kennengelernt. Abstrakt formuliert lautet die Frage: wieviele (Kamera-) Standorte braucht man zum Überblicken eines einfachen Polygons mit n Ecken, wobei ein Standort a einen Punkt p sieht“, falls die offene ” Strecke (a, p) ganz im Inneren des Polygons liegt. Ein Polygon ist dabei in der Sprache des vorigen Kapitels eine beschränkte Fläche einer geradlinigen planaren Karte. Ein Polygon heißt einfach, wenn es nur einen einzigen Randzyklus besitzt und dabei keine Ecke oder Kante doppelt vorkommt, wie hier im Bild angedeutet: Wie wir sehen werden, spielt die Einfachheit keine besondere Rolle, solange man nur richtig zählt, das heisst, als Komplexität eines Polygons nicht die Anzahl der Ecken oder Kanten sondern die Randlänge, das heisst die Anzahl der Halbkanten in allen Randzyklen zusammen definiert. Eine Triangulierung ist eine Zerlegung eine Polygons in Dreiecke, dass heisst in Polygone mit jeweils genau drei Ecken beziehungsweise Knoten, wobei keine zusätzlichen Knoten eingeführt werden. Zunächst zeigen wir noch, dass sich jedes Polygon triangulieren lässt und bestimmen die Anzahl der Dreiecke eine Triangulierung. 31 32 3. TRIANGULIERUNG VON POLYGONEN Satz 2. Jedes Polygon besitzt eine Triangulierung. Eine Triangulierung eines Polygons mit m Randzyklen und Randlänge n besteht immer aus genau n + 2m − 4 Dreiecken. Beweis. Wir zeigen zunächst, dass jedes Polygon mit mindestens 4 Ecken eine Diagonale besitzt, das heisst eine geradlinige Verbindung zwischen zwei Ecken, die ausser diesen nur innere Punkte des Polygons enthält. Hierzu sei p die am weitesten links liegende Ecke des Polygons. Falls diese nicht eindeutig ist, sei p von den am weitesten links liegenden Ecken die niedrigste. Dann ist der Innenwinkel an q auf jeden Fall kleiner als π. Es seien p und r die Nachbarecken von p gegen den beziehungsweise im Uhrzeigersinn und es sei s die Verbindungsstrecke zwischen p unf r. Falls s schon Diagonale ist, ist nichts weiter zu tun. Ansonsten sei v die am weitesten von s entfernte Ecke im Dreieck pqr. Dann muss die Verbindungsstrecke zwischen q und v eine Diagonale sein. r r s q q p s v p Es sei nun P ein Polygon mit m Randzyklen und n Halbkanten im Rand und d eine Diagonale von P . Dann zerlegt d das Polygon entweder in zwei Polygone mit kleinerer Randlänge, von denen keine eine grössere Anzahl von Randkomponenten besitzt oder d verbindet zwei Randzyklen miteinander. 1. DAS KLASSISCHE MUSEUMSPROBLEM 33 Wir nehmen nun an, der Satz über die Anzahl von Dreiecken sei für alle Polygone mit weniger Randzyklen oder kürzerem Rand bei gleicher Anzahl von Randkomponenten bewiesen. Im ersten Fall seien n0 und n00 die Randlängen der neuen Polygone P 0 und P 00 sowie m0 und m00 ihre Randzyklenanzahlen. Weiter seien t, t0 und t00 die Anzahlen von Dreiecken in Triangulierungen von P , P 0 und P 00 . Dann gilt n = n0 +n00 −2 und m = m0 +m00 −1 und damit nach Induktionvoraussetzung t = t0 + t00 = n0 + 2m0 − 4 + n00 + 2m00 − 4 = n + 2m − 4. Im zweiten Fall sei P 0 das neue Polygon, t0 die Anzahl von Dreiecken in einer Triangulierung von P 0 sowie n0 und m0 dessen Randlänge und Randzyklenzahl. Es gilt dann m = m0 + 1 und n = n0 − 2. Wieder nach Induktionsvoraussetzung ergibt sich t = t0 = n0 + 2m0 − 4 = n + 2 + 2m − 2 − 4 = n + 2m − 4. Wir liefern noch den Induktionsanfang nach: für ein Dreieck, also ein Polygon mit Randlänge n = 3 und genau einer Randkomponente gilt wie gefordert n + 2m − 4 = 3 + 2 − 4 = 1. Wir zeigen nun wie angekündigt, dass man ein einfaches Polygon der Randlänge n immer mit höchstens bn/3c Standorten überblicken kann. Dazu betrachten wir zunächst das innere Dual einer Triangulierung. Dies ist ein Graph mit genau einem Knoten in jedem Dreieck der Triangulierung, wobei zwei Knoten jweils durch eine Kante verbunden sind, wenn die dazugehörigen Dreiecke entlang einer gemeinsamen Diagonale liegen. Da ein einfaches Polygon durch jede Diagonale in zwei Teile zerfällt, ist in diesem Fall das innere Dual ein Baum. Dies erlaubt es, induktiv zu zeigen, dass sich die Ecken des Polygons mit den Farben Schwarz, Weiss und Grau färben lassen, wobei in jedem Dreieck der Triangulierung jede Farbe genau einmal vorkommt. Für ein Dreieck gilt das offensichtlich. Für eine Polygon mit mindestens 4 Ecken hat das innere Dual mindestens zwei Blätter. Entfernt 34 3. TRIANGULIERUNG VON POLYGONEN man die zu den Blättern gehörende Dreiecke und benutzt dann die Induktionsannahme, so sieht man unmittelbar, wie sich eine Färbung des verkleinerten Polygons zu einer des ursprünglichen ergänzen lässt. Es ist nun unmittelbar offensichtlich, dass erstens alle Ecken einer beliebigen Farbe zum Überblicken des Polygons ausreichen und das außerdem mindestens eine Farbe mit nicht mehr als bn/3c Ecken existiert. Wir erinnern noch an das Beispiel aus dem Einführungskapitel, welches zeigt, dass man eine Folge von Polygonen konstruieren kann, für welche bn/3c Standorte tatsächlich notwendig sind. Damit ist das klassische Art Gallery Theorem gezeigt: Satz 3. Zum Überblicken eines einfachen Polygons mit Randlänge n sind bn/3c Standorte immer ausreichend und manchmal auch notwendig. 2. Triangulierung mit Hilfe monotoner Polygone Wir wollen uns nun damit beschäftigen, wie man einfache Polygone möglichst effizient triangulieren kann. Am Beweis von Satz 2 erkennt man, dass eine Diagonale eines Polygons in linearer Zeit gefunden werden kann. Daraus folgt sofort ein O(n2 )-Triangulierungsalgorithmus. Andererseits wissen wir, dass man zum Beispiel konvexe Polygone leicht in linearer Zeit triangulieren kann. Wenn es also gelingt, ein beliebiges Polygon effizient in kleinere Teile zu zerlegen, die sich wiederum sehr schnell, zum Beispiel in linearer Zeit, triangulieren lassen, scheint ein Algorithmus mit besserer als quadratischer Laufzeit in Reichweite. 2. TRIANGULIERUNG MIT HILFE MONOTONER POLYGONE 35 Das Zerlegen in konvexe Teile ist leider nicht einfacher als das Triangulieren selbst. Es gibt aber eine allgemeinere Klasse von Polygonen, die sich ebenfalls relativ leicht in linearer Zeit triangulieren lassen, nämlich die sogenannten monotonen Polygone. Definition 1. Ein Polygon heisst monoton bezüglich einer Geraden G, falls es jede zu G senkrechte Gerade G0 in einem Punkt, einer Strecke oder überhaupt nicht schneidet. Im Folgenden nehmen wir für G immer die x-Achse. Monotone Polygone sind also grundsätzlich x-monoton, das heisst, sie schneiden jede vertikale Gerade in einem Punkt, einer Strecke oder überhaupt nicht. Monotone Polygone lassen sich durch eine lokale Eigenschaft charakterisieren. Unter einer Linksspitze eines Polygons P wollen wir eine Ecke v von P verstehen, deren Innenwinkel größer als π ist und von deren zwei Nachbarecken keine links von v liegt. Entsprechend verstehen wir unter einer Rechtsspitze eine Ecke mit Innenwinkel > π und ohne rechtsliegende Nachbarecke. Linksspitzen Rechtsspitzen 36 3. TRIANGULIERUNG VON POLYGONEN Lemma 3. Ein Polygon ohne Spitzen ist monoton. Beweis. Wir nehmen an, das Polygon P sei nicht monoton und zeigen dann, dass es Spitzen besitzt. Es sei also G eine vertikale Gerade, die einen nicht zusammenhängenden Schnitt mit P hat. Es seien weiter r, s und t Randpunkte von P auf G, so dass die offene Strecke (r, s) ganz innerhalb und die Strecke (s, t) ganz außerhalb von P liegt. Wir dürfen annehmen, dass r, s und t bezüglich ihrer y-Koordinaten aufsteigend sortiert sind. Verfolgt man nun den Rand von P von s aus gegen den Uhrzeigersinn, so erreicht man entweder zuerst r oder zuerst t. Wird zuerst t erreicht, so muss die am weitesten links liegende Ecke v auf diesem Weg zwischen s und t (beziehungsweise, wenn es mehrere solche gibt, von diesen mindestens die höchste oder niedrigste) eine Linksspitze sein. Andernfalls muss, wenn man den Rand von P von s aus im Uhrzeigersinn verfolgt, t vor s erreicht werden, da sonst r und s in einer anderen Randkomponente als t liegen würden, was bei einfachen Polygonen nicht möglich ist. In diesem Fall ist die am weitesten rechts liegende Ecke (beziehungsweise von diesen wieder die niedrigste oder höchste) zwischen s und t eine Rechtsspitze. t v t v s s r r Wie kann man nun ein monotones Polygon P in linearer Zeit triangulieren? Wir wollen zur Vereinfachung noch annehmen, dass keine zwei Ecken von P die gleiche x-Koordinate haben. Dann gibt es eine eindeutige am weitesten links liegende Ecke l und eine eindeutige am weitesten rechts liegende Ecke r. Der Rand von P zerfällt in zwei Streckenzüge, die beide jede vertikale Gerade in höchstens einem Punkt schneiden. Wir können also zum Beispiel durch einfaches Mischen die Ecken von P in linearer Zeit nach ihren x-Koordinaten sortieren. Der Triangulierungsalgorithmus beruht nun darauf, die Ecken 2. TRIANGULIERUNG MIT HILFE MONOTONER POLYGONE 37 von links nach rechts abzuarbeiten und immer die nächste erreichbare Diagonale, die ein Dreieck abtrennt, sofort zu benutzen. Algorithmus 3. Triangulieren eines monotonen Polygons P . Eingabe: Ein einfaches Polygon P . Ausgabe: Eine Liste von Diagonalen, die eine Triangulierung von P definieren. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. Sortiere die Ecken aus P von links nach rechts in ein Array A. Initialisiere eine Liste L mit den zwei ersten Ecken aus A. v := dritter Eintrag aus A. Solange v definiert ist: Falls v gegenüber der letzten Ecke in L liegt: Gib die Strecke von v zur zweiten Ecke in L aus. Entferne den ersten Eintrag aus L. Falls L nur noch ein Element hat: Hänge v an L an. v := nächster Eintrag in A. Sonst: w := letzte Ecke in L. /∗ w ist benachbart zu v. ∗/ Falls Innenwinkel an w ist < π: Gib Strecke von v zur zweitletzten Ecke in L aus, falls diese keine Kante ist. Entferne den letzten Eintrag aus L. Falls L nur noch ein Element hat: Hänge v an L an. v := nächster Eintrag in A. Sonst: /∗ Innenwinkel an w ist ≥ π ∗/ Hänge v an L an. v := nächster Eintrag in A. 1 2 3 4 5 38 3. TRIANGULIERUNG VON POLYGONEN Offensichtlich ist der Zeit- und Speicherbedarf für Algorithmus 3 linear. Zum Verständnis der Vorgehensweise ist es nützlich, sich vorzustellen, dass bei jeder Ausgabe einer Diagonalen ein Dreieck vom noch zu triangulierenden Teilpolygon Q abgeschnitten wird. So ist auch der in Zeile 13 erwähnte Innenwinkel bezüglich dieses Restpolygons zu verstehen. Die in der Liste L eingetragenen Ecken sind jeweils entlang des Randes von Q benachbart und haben, ausser der ersten und eventuell der letzten, einen Innenwinkel, der größer als π ist. Es bleibt nun die Aufgabe, ein beliebiges einfaches Polygon in monotone Teilpolygone zu zerlegen. Dies kann am einfachsten erreicht werden, indem man zunächst ein gegebenes Polygon in Trapeze zerlegt. Dabei wird von jeder Ecke aus eine Strecke so weit wie möglich nach oben und unten ausgedehnt, ohne das Polygon zu verlassen. Die nach dem Aufschneiden entlang dieser Strecken entstehenden Teile sind Trapeze, das heisst, sie haben zwei parallele gegenüberliegende Kanten, die in diesem Fall vertikal liegen. Ein Dreieck mit einer vertikalen Kante sehen wir dabei als degenerierten Spezialfall eines Trapezes an. Die Spitzen eines nichtmonotonen Polygons sind nun genau die Ecken, die innerhalb einer vertikalen Trapezkante liegen. Verbinden wir nun jede Linksspitze mit derjenigen Ecke, welche die linke Kante des links von ihr liegenden Trapezes definiert und entsprechend jede Rechtsspitze mit der Ecke, welche die rechte Kante des rechts von ihr liegenden Trapezes definiert, so erhalten wir damit alle Diagonalen, die zur Zerlegung des gegebenen Polygons in monotone Teile benötigt werden. Eine Trapezzerlegung lässt sich leicht mit Hilfe eines Plane-Sweep Algorithmus ähnlich den im vorigen Kapitel besprochenen konstruieren. Dies geht, wenn wie dort ein balancierter Suchbaum zum Speichern 3. KONVEXZERLEGUNG 39 der aktiven Kanten benutzt wird, inklusive Vorsortieren der Ecken nach ihren x-Koordinaten in der Laufzeit O(n log n). Es ist aber eigentlich unnötig, die Trapezzerlegung explizit zu konstruieren. Falls zum Beispiel jede x-Koordinate höchstens einmal vorkommt, so wird jedes Trapez der Zerlegung durch genau zwei Ecken, nämlich eine rechte und eine linke, definiert. Nur diese Paare von Ecken werden benötigt, um ein Polygon in monotone Teile zu zerlegen. Dazu speichert man während des Algorithmus für jede Kante k die letzte schon gesehene Ecke, die sich durch einen nach oben gerichteten Strahl durch das Innere des Polygons mit dieser Kante verbinden lässt. Diese heisst die Stützecke zu k. Falls keine solche Ecke existiert, wird das linke Ende von k als die Stützecke definiert. Stößt nun der Plane Sweep auf eine Linksspitze, so wird diese mit der Stützecke der darunter liegenden Kante verbunden. Für jede andere Ecke v wird getestet, ob diese rechtes Ende einer Kante k ist. Falls das Polygoninnere oberhalb von k liegt, sei dann w die Stützecke zu k. Andernfalls sei w die Stützecke der unter k liegenden Kante. Ist nun w eine Rechtsspitze, so wird wiederum w mit v verbunden. w v 3. Konvexzerlegung Wir betrachten nun noch kurz das Problem, ein Polygon in möglichst wenige konvexe Teile zu zerlegen. Es stellt sich als relativ schwer heraus, Algorithmen zum Auffinden optimaler Lösungen zu finden. Die bekannten Algorithmen haben darüber hinaus relativ hohe Laufzeiten. Man könnte deswegen versuchen, schnelle Algorithmen zu finden, von denen man zeigen kann, dass sie gegenüber dem Optimum nicht allzu schlecht abschneiden. Beim Triangulieren haben wir nur Diagonalen zum Zerlegen zugelassen. Dies war natürlich sinnvoll, weil sonst nur unnötige neue Ecken 40 3. TRIANGULIERUNG VON POLYGONEN eingeführt würden, welche die Anzahl der Dreiecke erhöhten. Beim Zerlegen in allgemeine konvexe Polygone kann es aber unter Umständen von Vorteil sein, durch Strecken zu Unterteilen, die nicht von Polygonecke zu Polygonecke gehen. Für die kleinste mögliche Anzahl konvexer Teile, in die sich ein gegebenes Polygon zerlegen lässt, gilt die folgende Abschätzung: Satz 4. Für ein Polygon P sei r die Anzahl seiner konkaven Ecken, das heisst der Ecken mit Innenwinkel > π, und K die kleinste Anzahl von konvexen Polygonen, in die sich P zerlegen lässt. Dann gilt dr/2e + 1 ≤ K ≤ r + 1. Beweis. Jede unterteilende Strecke kann maximal zwei konkave Ecken verschwinden lassen. Man braucht also zum Zerlegen in konvexe Teile mindestens dr/2e neue Kanten und erhielte dann dr/2e + 1 Teile. Unterteilt man andererseits von einer beliebigen konkaven Ecke aus entlang der inneren Winkelhalbierenden, so kann man damit induktiv eine Zerlegung mit genau r Strecken konstruieren, erhält also r + 1 konvexe Teile. Hertel und Mehlhorn haben einen eleganten und schnellen Algorithmus zur Konvexzerlegung angegeben, der nur um einen Faktor 3. KONVEXZERLEGUNG 41 schlechter ist als die optimale Zerlegung. Ist v eine Ecke eines durch Diagonalen in konvexe Teile zerlegten Polygons, so heiße eine dieser Diagonalen essentiell für v, falls ihr Weglassen ein Stück produzieren würde, das bei v eine nichtkonvexe Ecke hat. Dies kann offenbar nur dann passieren, wenn v einer der Endpunkte dieser Diagonalen und außerdem im ursprünglichen Polygon konkav ist. Eine Diagonale, die nicht essentiell für irgendeine Ecke ist, heiße inessentiell . Der Algorithmus von Hertel und Mehlhorn besteht nun einfach darin, beginnend mit einer Triangulierung so lange fortgesetzt inessentielle Diagonalen zu suchen und zu entfernen, bis alle verbleibenden Diagonalen für irgendeine Ecke essentiell sind. Weil eine essentielle Diagonale später nicht mehr inessentiell werden kann, geht das offenbar in linearer Zeit. Satz 5. Der Algorithmus von Hertel und Mehlhorn zerlegt ein Polygon P in maximal 2r + 1 konvexe Teile, wobei r die Anzahl der konkaven Ecken von P ist. Beweis. Wie man sich leicht überlegt, kann es für jede konkave Ecke höchstens zwei essentielle Diagonalen geben. Man benötigt also insgesamt maximal 2r Diagonalen und damit 2r + 1 Teile. Der Hertel-Mehlhorn-Algorithmus zerlegt also ein Polygon in höchstens viermal so viele konvexe Stücke wie eine optimale Zerlegung.