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 -Koordinate, dann kleinste -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:
| Zeile | Bedeutung |
|---|---|
| 3–4 | Startpunkt (unterster, dann linkester) |
| 6 | Erster Schritt: Es gibt noch keine vorige Kante, also misst man den Winkel zur Horizontalen durch (nach rechts). Der Punkt mit dem kleinsten Winkel ist die nächste Ecke , also die Schnur, die von rechts kommend als erste einen Punkt trifft |
| 9 | Allgemeiner Schritt: Verlängere die letzte Kante über hinaus. Der Punkt mit dem kleinsten Winkel zu dieser Verlängerung ist , die Schnur dreht sich um gegen den Uhrzeigersinn, bis sie anstößt |
| 10 | Ende, sobald man wieder bei 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.
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 aus ist ein besserer Kandidat als , wenn rechts von liegt, also .
Warum? Liegt rechts von , muss sich die Schnur (die gegen den Uhrzeigersinn dreht) zuerst an anlegen, bevor sie erreicht. 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 , also liegen alle links oder darauf. Nach dem Kantenkriterium (hier gegen den Uhrzeigersinn, also „alles links“) ist eine Hüllkante. Die vorige Kante muss man sich dafür gar nicht merken.
Durchgerechnetes Beispiel
Punkte: , , , , , dieselben wie bei Graham.
- Start: kleinstes haben A und E. Davon das kleinste : A.
- Von A: Welcher Punkt hat alle anderen links? E, denn läuft entlang der -Achse und alle anderen Punkte haben . → E
- Von E: Kandidat D. Prüfung: orient(E,D,A) , orient(E,D,B) , orient(E,D,C) . Alle liegen links. → D
- Von D: → B (A, C, E liegen links von )
- 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: Ecken, je ein Durchlauf über Punkte.
Dasselbe Beispiel zum Nachklicken (um verschoben):
Laufzeit (Folie 21)
Sei die Anzahl der Ecken der konvexen Hülle.
- Eine Ecke zu berechnen kostet : Der kleinste Winkel wird nur mit Arithmetik und Vergleichen über alle Punkte bestimmt.
- Es gibt Ecken.
| Fall | Laufzeit | Vergleich zu Graham | |
|---|---|---|---|
| Hülle ist Dreieck / Viereck | konstant | besser | |
| Zufallspunkte im Quadrat | erwartet | gleich | |
| Alle Punkte auf einem Kreis | viel schlechter |
- Für kleines ist der Algorithmus sehr effizient.
- Für großes ist er ineffizient, denn seine Worst-Case-Laufzeit ist .
- Ein solches Verhalten heißt output-sensitiv: Die Laufzeit hängt von der Größe der Ausgabe ab.
Faustregel: Jarvis schlägt Graham, wenn .
Frage der Folie: Welche Komplexität haben output-sensitive Hüllen-Algorithmen? Antwort im nächsten Abschnitt: .
Sonderfälle (Ergänzung)
| Fall | Problem | Lösung |
|---|---|---|
| Mehrere Punkte mit kleinstem | Ein Start auf einem Zwischenpunkt der unteren Kante wäre keine Ecke | bei Gleichstand kleinstes (so auf der Folie) |
| Kollineare Kandidaten () | Zwischenpunkte auf einer Kante würden als Ecken ausgegeben, oder die Suche springt hin und her | bei Gleichstand den weiter entfernten Punkt nehmen |
| Doppelte Punkte | Duplikat von hat Abstand 0 und ist kollinear mit allem | vorher entfernen |
| Rundungsfehler | Start wird evtl. nie wieder erreicht, Endlosschleife | zusätzlich abbrechen, wenn mehr als Ecken gefunden wurden |
| kein Polygon | direkt 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 Scan | Jarvis' March | |
|---|---|---|
| Prinzip | Sweep / inkrementell | Gift Wrapping |
| Vorverarbeitung | Sortieren | keine |
| Laufzeit | immer | |
| Worst Case | , optimal | |
| output-sensitiv | nein | ja |
| Ausgabe (Folien) | im Uhrzeigersinn ab (links) | gegen den Uhrzeigersinn ab (unten) |
| Startpunkt | kleinstes , dann | kleinstes , dann |