Kapitel 1-3

Werbung
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.
Herunterladen