Zum Hauptinhalt springen

2.5 Jarvis' March

Geschichte und Idee​

  • 1973 von Ray A. Jarvis entwickelt.
  • Bekannt als „Gift Wrapping“ (Geschenkpapier-Methode).

Idee: Man wickelt eine Schnur um die Punktmenge, bis sie mit der konvexen Hülle übereinstimmt. Man hält die Schnur an einem sicheren Hüllpunkt fest und dreht sie, bis sie an einen Punkt stößt. Das ist die nächste Ecke. Von dort weiter, bis man wieder am Anfang ist.

Unterschied zu Graham: Graham verarbeitet alle Eingabepunkte nacheinander. Jarvis erzeugt direkt die Ausgabe, eine Hüllecke nach der anderen. Pro Ecke wird einmal über alle Punkte gesucht.

Startpunkt​

Wie bei Graham's Scan startet man mit dem lexikographisch kleinsten Punkt. Der liegt garantiert auf der Hülle.

Bei Jarvis legt die Folie fest: zuerst kleinste yy-Koordinate, dann kleinste xx-Koordinate, also der unterste Punkt, bei Gleichstand der linkeste davon.

Warum liegt er sicher auf der Hülle? Kein Punkt liegt tiefer, also ist die waagrechte Gerade durch ihn eine Stützgerade. Unter den Punkten auf dieser Geraden ist er der linkeste, also ein Endpunkt und damit eine echte Ecke (kein Zwischenpunkt einer Kante).

Ausgabe: Jarvis' March berechnet die „extremen“ Punkte der Hülle gegen den Uhrzeigersinn (anders als Graham auf den Folien).

Algorithmus (Folie 20)​

 1  Polygon JarvisMarch(Points P)
2 {
3 Finde lexikographisch kleinsten Punkt p1;
4 q1 = p1;
5 i = 2;
6 qi = Endpunkt der Strecke p1 qi mit kleinstem Winkel
zur Horizontalen durch p1;
7 repeat {
8 i++;
9 qi = Endpunkt der Strecke q(i-1) qi mit kleinstem Winkel
zur Strecke q(i-2) q(i-1) in q(i-1);
10 } until (qi == p1);
11 Füge alle Punkte q1,...,qk in eine Liste L ein;
12 return L;
13 }

Zeile für Zeile:

ZeileBedeutung
3–4Startpunkt q1=p1q_1=p_1 (unterster, dann linkester)
6Erster Schritt: Es gibt noch keine vorige Kante, also misst man den Winkel zur Horizontalen durch p1p_1 (nach rechts). Der Punkt mit dem kleinsten Winkel ist die nächste Ecke q2q_2, also die Schnur, die von rechts kommend als erste einen Punkt trifft
9Allgemeiner Schritt: Verlängere die letzte Kante qi−2qi−1‾\overline{q_{i-2}q_{i-1}} über qi−1q_{i-1} hinaus. Der Punkt mit dem kleinsten Winkel zu dieser Verlängerung ist qiq_i, die Schnur dreht sich um qi−1q_{i-1} gegen den Uhrzeigersinn, bis sie anstößt
10Ende, sobald man wieder bei p1p_1 ankommt. Die Hülle ist geschlossen

Interaktiv durchspielen: Die Schnur dreht sich um die aktuelle Ecke (Ring). Gestrichelt ist der aktuelle Kandidat, rot schattiert seine rechte Seite. Liegt dort ein Punkt, wird er neuer Kandidat. ⏩ springt zur nächsten Ecke.

p1p2p3p4p5p6p7p8p9p10
Schritt 1/62: Start bei p5: kleinstes y, bei Gleichstand kleinstes x – liegt sicher auf der Hülle.
Klicken: hinzufügen · Ziehen: verschieben · Rechtsklick: löschen
bisherige Hülleaktueller Kandidatrechte Seite (muss leer sein)besserer Kandidat

Kleinster Winkel = Orientierungstest (Ergänzung)​

Wörtlich Winkel zu berechnen (atan2, acos) ist teuer und fehleranfällig. Es geht äquivalent ohne Winkel:

Von der aktuellen Ecke pp aus ist rr ein besserer Kandidat als qq, wenn rr rechts von pq⃗\vec{pq} liegt, also orient⁡(p,q,r)<0\operatorname{orient}(p,q,r)<0.

Warum? Liegt rr rechts von pq⃗\vec{pq}, muss sich die Schnur (die gegen den Uhrzeigersinn dreht) zuerst an rr anlegen, bevor sie qq erreicht. rr hat also den kleineren Winkel.

Damit wird jeder Schritt zu einer Maximumsuche mit dem Orientierungstest als Vergleich:

q = irgendein Punkt ≠ p
for alle Punkte r ≠ p:
if (r liegt rechts von pq) q = r

Am Ende liegt kein Punkt mehr rechts von pq⃗\vec{pq}, also liegen alle links oder darauf. Nach dem Kantenkriterium (hier gegen den Uhrzeigersinn, also „alles links“) ist pq‾\overline{pq} eine Hüllkante. Die vorige Kante muss man sich dafür gar nicht merken.

Durchgerechnetes Beispiel​

Punkte: A(0,0)A(0,0), B(1,2)B(1,2), C(2,1)C(2,1), D(3,3)D(3,3), E(4,0)E(4,0), dieselben wie bei Graham.

  1. Start: kleinstes y=0y=0 haben A und E. Davon das kleinste xx: A.
  2. Von A: Welcher Punkt hat alle anderen links? E, denn AE⃗\vec{AE} läuft entlang der xx-Achse und alle anderen Punkte haben y>0y>0. → E
  3. Von E: Kandidat D. Prüfung: orient(E,D,A) =(−1)(0)−(3)(−4)=12>0=(-1)(0)-(3)(-4)=12>0, orient(E,D,B) =(−1)(2)−(3)(−3)=7>0=(-1)(2)-(3)(-3)=7>0, orient(E,D,C) =(−1)(1)−(3)(−2)=5>0=(-1)(1)-(3)(-2)=5>0. Alle liegen links. → D
  4. Von D: → B (A, C, E liegen links von DB⃗\vec{DB})
  5. Von B: → A = Startpunkt, Ende.

Ergebnis: A → E → D → B, gegen den Uhrzeigersinn. Das sind dieselben Ecken wie bei Graham (A B D E), nur andersherum. ✔

Aufwand: h=4h=4 Ecken, je ein Durchlauf über n=5n=5 Punkte.

Dasselbe Beispiel zum Nachklicken (um (5,2)(5,2) verschoben):

ABCDE
Schritt 1/22: Start bei A: kleinstes y, bei Gleichstand kleinstes x – liegt sicher auf der Hülle.
Klicken: hinzufügen · Ziehen: verschieben · Rechtsklick: löschen
bisherige Hülleaktueller Kandidatrechte Seite (muss leer sein)besserer Kandidat

Laufzeit (Folie 21)​

Sei hh die Anzahl der Ecken der konvexen Hülle.

  • Eine Ecke qiq_i zu berechnen kostet O(n)O(n): Der kleinste Winkel wird nur mit Arithmetik und Vergleichen über alle Punkte bestimmt.
  • Es gibt hh Ecken.

⇒Gesamtlaufzeit O(h⋅n).\Rightarrow\quad \text{Gesamtlaufzeit } O(h\cdot n).

FallhhLaufzeitVergleich zu Graham
Hülle ist Dreieck / ViereckkonstantO(n)O(n)besser
Zufallspunkte im Quadraterwartet O(log⁡n)O(\log n)O(nlog⁡n)O(n\log n)gleich
Alle Punkte auf einem KreisnnO(n2)O(n^2)viel schlechter
  • Für kleines hh ist der Algorithmus sehr effizient.
  • Für großes hh ist er ineffizient, denn seine Worst-Case-Laufzeit ist O(n2)O(n^2).
  • Ein solches Verhalten heißt output-sensitiv: Die Laufzeit hängt von der Größe der Ausgabe ab.

Faustregel: Jarvis schlägt Graham, wenn h<log⁡nh<\log n.

Frage der Folie: Welche Komplexität haben output-sensitive Hüllen-Algorithmen? Antwort im nächsten Abschnitt: Θ(nlog⁡h)\Theta(n\log h).

Sonderfälle (Ergänzung)​

FallProblemLösung
Mehrere Punkte mit kleinstem yyEin Start auf einem Zwischenpunkt der unteren Kante wäre keine Eckebei Gleichstand kleinstes xx (so auf der Folie)
Kollineare Kandidaten (orient⁡=0\operatorname{orient}=0)Zwischenpunkte auf einer Kante würden als Ecken ausgegeben, oder die Suche springt hin und herbei Gleichstand den weiter entfernten Punkt nehmen
Doppelte PunkteDuplikat von pp hat Abstand 0 und ist kollinear mit allemvorher entfernen
RundungsfehlerStart wird evtl. nie wieder erreicht, Endlosschleifezusätzlich abbrechen, wenn mehr als nn Ecken gefunden wurden
n≤2n\le2kein Polygondirekt zurückgeben

In C++ (Ergänzung)​

Umsetzung von Folie 20 mit Orientierungstest statt Winkel
double dist2(const PointF& a, const PointF& b)
{
const double dx = b.x - a.x, dy = b.y - a.y;
return dx * dx + dy * dy;
}

std::vector<PointF> jarvisMarch(std::vector<PointF> P)
{
std::sort(P.begin(), P.end(), lexLess); // nur fürs Entfernen von Duplikaten
P.erase(std::unique(P.begin(), P.end(), samePoint), P.end());
const size_t n = P.size();
if (n < 3) return P;

size_t start = 0; // Zeile 3: kleinstes y, dann kleinstes x
for (size_t i = 1; i < n; ++i)
if (P[i].y < P[start].y || (P[i].y == P[start].y && P[i].x < P[start].x))
start = i;

std::vector<PointF> hull;
size_t p = start;
do { // Zeilen 7–10
hull.push_back(P[p]);
size_t q = (p + 1) % n;
for (size_t r = 0; r < n; ++r) {
if (r == p) continue;
const double o = orient(P[p], P[q], P[r]);
if (o < 0 || (o == 0 && dist2(P[p], P[r]) > dist2(P[p], P[q])))
q = r; // r hat kleineren Winkel
}
p = q;
} while (p != start && hull.size() <= n);
return hull; // gegen den Uhrzeigersinn
}

(orient, lexLess, samePoint wie bei Graham's Scan.)

Graham vs. Jarvis​

Graham's ScanJarvis' March
PrinzipSweep / inkrementellGift Wrapping
VorverarbeitungSortierenkeine
LaufzeitO(nlog⁡n)O(n\log n) immerO(h⋅n)O(h\cdot n)
Worst CaseO(nlog⁡n)O(n\log n), optimalO(n2)O(n^2)
output-sensitivneinja
Ausgabe (Folien)im Uhrzeigersinn ab p1p_1 (links)gegen den Uhrzeigersinn ab p1p_1 (unten)
Startpunktkleinstes xx, dann yykleinstes yy, dann xx