Zum Hauptinhalt springen

2.6 Chan's Algorithmus

Die Frage​

Welche Komplexität haben output-sensitive Algorithmen für die konvexe Hülle?

  • Untere Schranke: Ω(nlog⁡h)\Omega(n\log h) [Kirkpatrick & Seidel, 1986]. Kirkpatrick und Seidel haben selbst einen Algorithmus mit O(nlog⁡h)O(n\log h) angegeben („The ultimate planar convex hull algorithm?“), der aber kompliziert ist.
  • Einfach erreicht wird die Schranke durch Chan's Algorithmus (Timothy Chan, 1995/96), auch Nielsens Algorithmus genannt (Frank Nielsen hat ihn unabhängig gefunden). Er ist eine Kombination aus Graham's Scan und Jarvis' March.
  • Optimierungen stammen von Emo Welzl.

Idee​

Jarvis ist langsam, weil jeder Wrapping-Schritt alle nn Punkte anschaut. Chan beschleunigt diesen Schritt:

  1. Vorverarbeitung mit Graham: Teile die nn Punkte in k=n/mk=n/m Gruppen zu je höchstens mm Punkten und berechne für jede Gruppe die Hülle mit Graham. Kosten: O(nm⋅mlog⁡m)=O(nlog⁡m)O(\tfrac{n}{m}\cdot m\log m)=O(n\log m).
  2. Wrapping mit Jarvis über die Gruppenhüllen: Statt alle nn Punkte zu prüfen, sucht man in jeder Gruppenhülle den besten Kandidaten. Da diese konvex und sortiert ist, geht das per binärer Suche in O(log⁡m)O(\log m). Pro Wrapping-Schritt kostet das also O(nmlog⁡m)O(\tfrac{n}{m}\log m) statt O(n)O(n).
  3. Problem: Man kennt hh nicht, also weiß man nicht, wie man mm wählen soll. Lösung: hh raten und die Schätzung schnell steigern.

Algorithmus (Folie 23)​

 1  Polygon ChansAlgorithm(Points P)
2 {
3 for (t = 1, 2, ... bis Hülle gefunden) {
4 H = min(2^(2^t), n); // Schätzung für die Eckenzahl h
5 Partitioniere P in k = n/m Teilmengen P1,...,Pk der Größe ≤ m;
6 Berechne CH(P1),...,CH(Pk) mit Graham's Scan;
7 Finde lexikographisch kleinsten Punkt p1; p0 = (−∞, 0);
8 for (j = 1, ..., H) { // versuche eine Hülle mit ≤ H Ecken
9 for (i = 1, ..., k) // Wrapping von CH(P1),...,CH(Pk)
10 Berechne qi ∈ CH(Pi)\{pj}, das den Winkel ∢ p(j-1) pj qi maximiert,
mit "binärer Suche" auf CH(Pi);
11 p(j+1) = der Punkt aus {q1,...,qk}, der ∢ p(j-1) pj q maximiert;
12 if (p(j+1) == p1) return Hülle gefunden;
13 } // H zu klein, keine Hülle mit H Ecken gefunden
14 } // Schätzung für h verbessern
15 }

Struktur (Folie 24):

ZeilenTeil
3–4Iteration, um die unbekannte Eckenzahl hh zu finden
5–6Vorverarbeitung: Graham's Scan auf den Gruppen
7–13Jarvis' March über die Gruppenhüllen, abgebrochen nach HH Schritten

Erläuterungen:

  • Mit m=Hm=H ist die Gruppengröße gleich der geschätzten Eckenzahl.
  • p0=(−∞,0)p_0=(-\infty,0) ist ein Hilfspunkt, damit der erste Winkel (wie bei Jarvis zur Horizontalen) dieselbe Formel benutzt.
  • Ist nach HH Schritten die Hülle nicht geschlossen, war die Schätzung zu klein. Man bricht ab und startet mit größerem HH neu. Dadurch kostet eine Runde nie mehr als HH Wrapping-Schritte.

Interaktiv durchspielen: Die Farben sind die Gruppen, jede mit ihrer Teilhülle. In jedem Wrapping-Schritt sucht man in jeder Gruppe den besten Kandidaten (gestrichelt) und nimmt davon den besten (Pfeil). Reicht HH nicht, wird die Runde rot abgebrochen und HH quadriert.

Schritt 1/18: Runde t = 1: H = m = min(2^(2^1), n) = 4. Zerlege in k = ⌈40/4⌉ = 10 Gruppen und berechne deren Hüllen mit Graham.
t = 1, H = m = 4, k = 10 Gruppen

Laufzeit einer Runde (Folie 25)​

ZeileTeilAufwand
5PartitionierenO(n)O(n)
6Hüllen der GruppenO(nm⋅mlog⁡m)O\big(\tfrac{n}{m}\cdot m\log m\big)
7Wrapping initialisierenO(n)O(n)
8IterationenO(H)O(H)
9IterationenO(n/m)O(n/m)
10Wrapping jeder Hülle (binäre Suche)O(log⁡m)O(\log m)
11Wrapping der Hüllen (bestes qiq_i)O(n/m)O(n/m)

Zusammen für Zeilen 4–13:

O(n+nlog⁡m+n+H(nmlog⁡m+nm))=O(nlog⁡m+H nmlog⁡m).O\Big(n + n\log m + n + H\big(\tfrac{n}{m}\log m + \tfrac{n}{m}\big)\Big) = O\Big(n\log m + H\,\tfrac{n}{m}\log m\Big).

Wahl von mm und HH (Folie 26)​

1. Mit m=Hm=H wird eine Runde zu O(nlog⁡H+nlog⁡H)=O(nlog⁡H)O(n\log H + n\log H) = O(n\log H).

Die Zeilen 3–4 laufen, bis H≥hH\ge h ist (oder schlimmstenfalls H=nH=n):

h≤H=22t  ⟺  log⁡2h≤2t  ⟺  log⁡2log⁡2h≤t.h\le H=2^{2^t}\iff \log_2 h\le 2^t\iff \log_2\log_2 h\le t.

Gesamtlaufzeit über alle Runden:

∑t=1⌈log⁡log⁡h⌉O(nlog⁡22t)=O(n∑t=1⌈log⁡log⁡h⌉2t)=O(n⋅2log⁡log⁡h)=O(nlog⁡h).\sum_{t=1}^{\lceil\log\log h\rceil} O\big(n\log 2^{2^t}\big) = O\Big(n\sum_{t=1}^{\lceil\log\log h\rceil} 2^t\Big) = O\big(n\cdot 2^{\log\log h}\big) = O(n\log h).

Warum 22t2^{2^t} (Quadrieren) statt 2t2^t (Verdoppeln)? Eine Runde kostet O(nlog⁡H)O(n\log H). Beim Quadrieren verdoppelt sich log⁡H\log H von Runde zu Runde (2t2^t). Die Kosten bilden eine geometrische Reihe, die von der letzten Runde dominiert wird, und die kostet O(nlog⁡h)O(n\log h). Beim Verdoppeln von HH würde log⁡H\log H nur um 1 wachsen, man hätte log⁡h\log h Runden à bis zu nlog⁡hn\log h, insgesamt O(nlog⁡2h)O(n\log^2 h).

2. Heuristisch besser in Zeile 4: H=min⁡(2t2, n)H=\min\big(2^{t^2},\,n\big), das wächst weniger aggressiv.

3. Gute Balance zwischen mm und HH: m=min⁡(Hlog⁡H, n)m=\min(H\log H,\ n).

4. In Zeile 10 kann man lineare Suche statt binärer Suche nehmen („Hülle der Hüllen“). Das ist einfacher zu implementieren und ändert die Gesamtlaufzeit nicht [Welzl], denn die Laufzeit für das Wrapping ist ohnehin kleiner.

Einordnung​

AlgorithmusLaufzeithh kleinh=nh=n
GrahamO(nlog⁡n)O(n\log n)nlog⁡nn\log nnlog⁡nn\log n
JarvisO(nh)O(nh)≈n\approx nn2n^2
ChanO(nlog⁡h)O(n\log h)≈n\approx nnlog⁡nn\log n

Chan ist immer mindestens so gut wie der bessere der beiden und erreicht die untere Schranke Ω(nlog⁡h)\Omega(n\log h). Er ist also optimal output-sensitiv.