Das Problem des dichtestenPunktpaares (englischclosest pair of points problem) ist die Suche nach den zwei am dichtesten beieinander liegenden Punkten in einer Ebene. Gegeben ist eine beliebige Menge von Punkten in der Ebene und gesucht sind zwei dieser Punkte, sodass der euklidische Abstand minimal ist. Ein ähnliches Problem ist die Suche nach den zwei am weitesten voneinander entfernten Punkten in der Ebene, also den zwei Punkten mit dem maximalen euklidischen Abstand.
Der Brute-force-Algorithmus berechnet die Abstände zwischen allen möglichen Punktpaaren und wählt das Punktpaar mit dem kleinsten Abstand aus. Wenn die Anzahl der Punkte ist, gibt es mögliche Punktpaare, für die die Abstände berechnet werden müssen. Die Laufzeit des Algorithmus ist also quadratisch und liegt in .
Der Divide-Schritt teilt die nach x-Koordinaten sortierte Liste in zwei Hälften und links und rechts des Medians auf.
Der rekursive Aufruf geschieht nun jeweils für die beiden Hälften. Man erhält das jeweils dichteste Punktpaar beider Hälften, ohne dass eventuelle Punktpaare zwischen beiden Hälften berücksichtigt werden.
Der Conquer-Schritt kombiniert nun diese beiden Hälften. Es wird das Punktpaar mit dem kleinsten gefundenen Abstand ausgewählt. Hierbei sind zwei Fälle zu beachten:
Das Punktpaar liegt in der linken oder der rechten Hälfte. In diesem Fall gibt es keine weiteren Probleme.
Es gibt zwei Punkte in unterschiedlichen Hälften, deren Abstand kleiner ist, als der bisher in den Hälften gefundene. Dieser Fall ist nun gesondert zu berücksichtigen. Datei:Punkteinfuegen d and c.jpgPrüfung der Nachbarn von P
Es ist nicht nötig, alle solchen Punktpaare einzeln durchzuprüfen. Ist der kleinste gefundene Abstand in einer der beiden Hälften, dann ist dies eine obere Grenze für den kleinsten Abstand. Daher müssen nur noch Punkte betrachtet werden, deren Abstand zum Median in x-Richtung höchstens beträgt. Außerdem müssen für diese Punkte nur noch Partner betrachtet werden, deren Abstand in y-Richtung kleiner als ist. Daraus ergibt sich für jeden dieser Punkte, dass lediglich der Abstand zu einer konstanten Anzahl von anderen Punkten zu prüfen ist: Denkt man sich ein Gitter der Weite in der Umgebung des Medians, dann kann in jeder Gitterzelle nur einer dieser Punkte liegen, weil sonst bereits ein Abstand kleiner als gefunden worden wäre. Weil nur diejenigen Gitterzellen zu prüfen sind, die von P aus höchstens Abstand haben, sind maximal 24 weitere Abstände für jeden Punkt zu berechnen, nämlich jeweils maximal 12 Abstände nach oben und nach unten, womit statt quadratischer Laufzeit nur noch lineare Laufzeit notwendig ist. Insgesamt ergibt sich für die Anzahl der berechneten Abstände die Rekursionsgleichung , sodass die Laufzeit in liegt.
Die FunktiongetSmallestDistanceBruteForce verwendet für Teilbereiche aus 2 oder 3 Punkten den Brute-force-Algorithmus. Der Conquer-Schritt wird von der rekursiven Funktion getSmallestDistanceRecursive durchgeführt, die sich selbst für die linke Hälfte und für die rechte Hälfte aufruft. Die Funktion getSmallestDistanceOfStrip bestimmt das Punktpaar und den kleinsten Abstand für den Streifen, der Punkte aus beiden Hälften enthält. Die Laufzeit dieser Funktion ist nicht linear zur Anzahl der Punkte, da sortiert wird. Dadurch ist die Laufzeit dieser Implementierung
.
#include<iostream>#include<sstream>usingnamespacestd;// Deklariert den Datentyp für Punkte mit x-Koordinate und y-Koordinate in der EbenestructPoint{intx,y;};// Vergleichsfunktion für das Sortieren der Punkte nach der x-KoordinateintcompareX(constvoid*a,constvoid*b){Point*point1=(Point*)a,*point2=(Point*)b;returnpoint1->x-point2->x;}// Vergleichsfunktion für das Sortieren der Punkte nach der y-KoordinateintcompareY(constvoid*a,constvoid*b){Point*point1=(Point*)a,*point2=(Point*)b;return(point1->y-point2->y);}// Diese Funktion berechnet den euklidischen Abstand zwischen zwei PunktendoublegetDistance(Pointpoint1,Pointpoint2){returnsqrt((point1.x-point2.x)*(point1.x-point2.x)+(point1.y-point2.y)*(point1.y-point2.y));}// Diese Funktion gibt den kleinsten Abstand der Punkte bis zum gegebenen Index zurück, indem alle Paare von Punkten durchlaufen und geprüft werdendoublegetSmallestDistanceBruteForce(Pointpoints[],Point&point1,Point&point2,intindex){doubleminimumDistance=FLT_MAX;for(inti=0;i<index;i++){for(intj=i+1;j<index;j++){// Die for-Schleifen durchlaufen alle Paare von PunktendoublecurrentDistance=getDistance(points[i],points[j]);// Ruft die Funktion für die Berechnung des euklidischen Abstands aufif(currentDistance<minimumDistance)// Prüft, ob der Abstand zwischen den aktuellen Punkten kleiner als der bisher kleinste Abstand ist{point1=points[i];point2=points[j];minimumDistance=currentDistance;}}}returnminimumDistance;}// Diese Funktion gibt den kleinsten Abstand der Punkte im gegebenen Streifen zurück, wenn der Abstand kleiner als der vorgegebene Abstand istdoublegetSmallestDistanceOfStrip(Pointpoints[],Point&point1,Point&point2,intindex,doubledistance){doubleminimumDistance=distance;// Initialisiert den kleinsten Abstandqsort(points,index,sizeof(Point),compareY);// Sortiert die Punkte nach der y-Koordinate mithilfe der Vergleichsfunktion compareYfor(inti=0;i<index;i++){for(intj=i+1;j<index&&points[j].y-points[i].y<minimumDistance;j++)// Durchläuft die Punkte, so lange die Differenz der y-Koordinaten kleiner als der vorgegebene Abstand ist{doublecurrentDistance=getDistance(points[i],points[j]);// Ruft die Funktion für die Berechnung des euklidischen Abstands aufif(currentDistance<minimumDistance)// Prüft, ob der Abstand zwischen den aktuellen Punkten kleiner als der bisher kleinste Abstand ist{point1=points[i];point2=points[j];minimumDistance=currentDistance;}}}returnminimumDistance;}// Diese rekursive Funktion gibt den kleinsten Abstand der Punkte zurück. Sie verwendet das Teile-und-herrsche-Verfahren, indem sie die Menge der Punkte in zwei Hälften teilt und den kleinsten Abstand für die beiden Hälften und den Streifen zwischen den beiden Hälften berechnet.doublegetSmallestDistanceRecursive(Pointpoints[],Point&point1,Point&point2,intindex){if(index<=3){returngetSmallestDistanceBruteForce(points,point1,point2,index);// Wenn das Array aus 2 oder 3 Punkten besteht, wird die Brute Force Funktion aufgerufen}intmedianIndex=index/2;PointmedianPoint=points[medianIndex];PointleftPoint1,leftPoint2,rightPoint1,rightPoint2;// Deklariert Referenzen für die Punkte mit dem kleinsten AbstanddoubleleftDistance=getSmallestDistanceRecursive(points,leftPoint1,leftPoint2,medianIndex);// Rekursiver Aufruf der Funktion für die linke Hälfte der PunktedoublerightDistance=getSmallestDistanceRecursive(points+medianIndex,rightPoint1,rightPoint2,index-medianIndex);// Rekursiver Aufruf der Funktion für die rechte Hälfte der Punkte// Bestimmt den kleineren der beiden Abstände und weist die Referenzen auf die Punkte zudoubledistance;if(leftDistance<rightDistance)// Wenn der Abstand in der linken Hälfte kleiner ist{point1=leftPoint1;point2=leftPoint2;distance=leftDistance;}else{point1=rightPoint1;point2=rightPoint2;distance=rightDistance;}// Erzeugt ein Array mit einem Streifen von Punkten auf, deren x-Koordinate einen kleineren Abstand hatPoint*stripPoints=newPoint[index];intj=0;for(inti=0;i<index;i++){if(abs(points[i].x-medianPoint.x)<distance)// Prüft, ob der Abstand der x-Koordinate zwischen den aktuellen Punkten kleiner als der bisher kleinste Abstand ist{stripPoints[j]=points[i];j++;}}getSmallestDistanceOfStrip(stripPoints,point1,point2,j,distance);// Aufruf der Funktion für den Streifen von Punkten aus beiden Hälften. Wenn der Abstand in diesem Streifen kleiner ist, wird dieser zurückgegeben.}// Diese Funktion gibt den kleinsten Abstand der Punkte und Referenzen auf die zwei Punkte mit dem kleinsten Abstand zurückdoublegetSmallestDistance(Pointpoints[],Point&point1,Point&point2,intnumberOfPoints){qsort(points,numberOfPoints,sizeof(Point),compareX);// Sortiert die Punkte nach der x-Koordinate mithilfe der Vergleichsfunktion compareXreturngetSmallestDistanceRecursive(points,point1,point2,numberOfPoints);// Aufruf der rekursiven Funktion}// Diese Funktion gibt den Punkt in der Form (x, y) zurückstringpointToString(Point*point){stringstreamtext;text<<"("<<point->x<<", "<<point->y<<")"<<endl;returntext.str();// Typumwandlung von stringstream nach string}// Hauptfunktion die das Programm ausführtintmain(){Pointpoints[]={{2,3},{12,30},{40,50},{5,1},{12,10},{3,4}};// Deklariert und initialisiert ein Array mit 6 PunktenintnumberOfPoints=sizeof(points)/sizeof(points[0]);Pointpoint1,point2;// Deklariert Referenzen für die zwei Punkte mit dem kleinsten Abstandcout<<"Der kleinste Abstand zwischen zwei Punkten ist "<<getSmallestDistance(points,point1,point2,numberOfPoints)<<endl;// Aufruf der Funktion, Ausgabe auf der Konsolecout<<"Erster Punkt: "<<pointToString(&point1);// Ausgabe auf der Konsolecout<<"Zweiter Punkt: "<<pointToString(&point2);// Ausgabe auf der Konsole}
#include<iostream>#include<vector>#include<algorithm>#include<cmath>#include<cfloat>usingnamespacestd;structPoint{intx,y;};// Überschreibt den << Operator, um einen Punkt im Format (x, y) auszugebenostream&operator<<(ostream&os,constPoint&p){os<<"("<<p.x<<", "<<p.y<<")";returnos;}// Berechnet die euklidische Distanz zwischen zwei PunktendoublegetDistance(constPoint&p1,constPoint&p2){returnsqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));}// Eine Brute-Force Methode für kleine EingabegrößendoublebruteForce(constvector<Point>&points,Point&best1,Point&best2){doubleminDist=FLT_MAX;intn=points.size();for(inti=0;i<n;i++){for(intj=i+1;j<n;j++){doubled=getDistance(points[i],points[j]);if(d<minDist){minDist=d;best1=points[i];best2=points[j];}}}returnminDist;}// Gegeben ist ein Streifen von Punkten (nach y sortiert).// Finde das nächstgelegene Punktpaar im Streifen.doublestripClosest(constvector<Point>&strip,doubled,Point&best1,Point&best2){doubleminDist=d;intn=strip.size();for(inti=0;i<n;i++){// Da der Streifen nach y sortiert ist, werden nur Punkte verglichen, deren y-Differenz kleiner als minDist ist.// Hier sollte nochmals hervorgehoben werden, dass die innere Schleife abbricht, sobald die Distanz überschritten wird.// Genau dies macht die Anzahl an Iterationen in dieser Schleife konstant.for(intj=i+1;j<n&&(strip[j].y-strip[i].y)<minDist;j++){doubledCurrent=getDistance(strip[i],strip[j]);if(dCurrent<minDist){minDist=dCurrent;best1=strip[i];best2=strip[j];}}}returnminDist;}// Rekursive Funktion, die die kleinste Distanz berechnet.// Die Funktion erhält sowohl die Punkte sortiert nach x (Px) als auch nach y (Py).doubleclosestPairRec(vector<Point>&Px,vector<Point>&Py,Point&best1,Point&best2){intn=Px.size();if(n<=3){returnbruteForce(Px,best1,best2);}intmid=n/2;PointmidPoint=Px[mid];// Teile die Punkte in Px in linke und rechte Hälften.vector<Point>Qx(Px.begin(),Px.begin()+mid);vector<Point>Rx(Px.begin()+mid,Px.end());// Teile Py in Qy und Ry, sodass Qy die Punkte aus Qx enthält und Ry die Punkte aus Rx.// Dadurch bleibt die Sortierung nach y erhalten.vector<Point>Qy,Ry;for(constauto&p:Py){if(p.x<=midPoint.x){Qy.push_back(p);}else{Ry.push_back(p);}}Pointbest1Left,best2Left,best1Right,best2Right;doubledLeft=closestPairRec(Qx,Qy,best1Left,best2Left);doubledRight=closestPairRec(Rx,Ry,best1Right,best2Right);doubled;if(dLeft<dRight){d=dLeft;best1=best1Left;best2=best2Left;}else{d=dRight;best1=best1Right;best2=best2Right;}// Erzeuge den Streifen: Punkte in Py, deren x-Koordinate innerhalb von d um midPoint.x liegt.vector<Point>strip;for(constauto&p:Py){if(abs(p.x-midPoint.x)<d){strip.push_back(p);}}// Überprüfe den Streifen auf ein näherliegendes Paar.doubledStrip=stripClosest(strip,d,best1,best2);returnmin(d,dStrip);}// Die Hauptfunktion, die die sortierten Arrays vorbereitet und die rekursive Funktion aufruft.doubleclosestPair(vector<Point>&points,Point&best1,Point&best2){vector<Point>Px=points;// Punkte sortiert nach xvector<Point>Py=points;// Punkte sortiert nach ysort(Px.begin(),Px.end(),[](constPoint&a,constPoint&b){returna.x<b.x;});sort(Py.begin(),Py.end(),[](constPoint&a,constPoint&b){returna.y<b.y;});returnclosestPairRec(Px,Py,best1,best2);}intmain(){vector<Point>points={{2,3},{12,30},{40,50},{5,1},{12,10},{3,4}};Pointbest1,best2;doubledist=closestPair(points,best1,best2);cout<<"Kleinste Distanz: "<<dist<<"\n";cout<<"Nächstgelegenes Paar: "<<best1<<" und "<<best2<<"\n";return0;}
Folgende Implementierung hat Speicherverbrauch.
Code-Schnipsel
#include<iostream>#include<vector>#include<algorithm>#include<cmath>#include<cfloat>#include<iterator>usingnamespacestd;structPoint{intx,y;};// Überschreibt den << Operator, um einen Punkt im Format (x, y) auszugebenostream&operator<<(ostream&os,constPoint&p){os<<"("<<p.x<<", "<<p.y<<")";returnos;}// Berechnet die euklidische Distanz zwischen zwei PunktendoublegetDistance(constPoint&p1,constPoint&p2){returnsqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));}// Brute-Force-Lösung für sehr kleine Bereiche (weniger als oder gleich 3 Punkte)// Diese Version arbeitet mit Iteratoren für den Bereich [xBegin, xEnd)template<typenameIt>doublebruteForce(ItxBegin,ItxEnd,Point&best1,Point&best2){doubleminDist=FLT_MAX;for(autoit1=xBegin;it1!=xEnd;++it1){for(autoit2=next(it1);it2!=xEnd;++it2){doubled=getDistance(*it1,*it2);if(d<minDist){minDist=d;best1=*it1;best2=*it2;}}}returnminDist;}// Überprüft den Streifen (bereits nach y sortiert) auf ein näherliegendes Paar.// Arbeitet mit Iteratoren, um keine zusätzlichen Kopien anzulegen.template<typenameIt>doublestripClosest(Itbegin,Itend,doubled,Point&best1,Point&best2){doubleminDist=d;for(autoit1=begin;it1!=end;++it1){autoit2=next(it1);for(;it2!=end&&((it2->y-it1->y)<minDist);++it2){doubledCurrent=getDistance(*it1,*it2);if(dCurrent<minDist){minDist=dCurrent;best1=*it1;best2=*it2;}}}returnminDist;}// Rekursive Funktion, die das nächstgelegene Punktpaar findet.// Es werden Iteratoren für den x‑sortierten Bereich [xBegin, xEnd)// und den y‑sortierten Bereich [PyBegin, PyEnd) übergeben. // 'aux' ist ein Hilfsvektor, der einmalig O(n) Speicher reserviert und wiederverwendet wird.doubleclosestPairRec(vector<Point>::iteratorxBegin,vector<Point>::iteratorxEnd,vector<Point>::iteratorPyBegin,vector<Point>::iteratorPyEnd,vector<Point>&aux,Point&best1,Point&best2){intn=distance(xBegin,xEnd);if(n<=3){returnbruteForce(xBegin,xEnd,best1,best2);}// Bestimme den mittleren Iterator im x‑sortierten Bereich.autoxMid=xBegin+n/2;PointmidPoint=*xMid;// In-Place Partitionierung von Py:// Teile den Bereich [PyBegin, PyEnd) so, dass alle Punkte mit p.x <= midPoint.x am Anfang stehen.automidIt=stable_partition(PyBegin,PyEnd,[&](constPoint&p){returnp.x<=midPoint.x;});Pointbest1Left,best2Left,best1Right,best2Right;doubledLeft=closestPairRec(xBegin,xMid,PyBegin,midIt,aux,best1Left,best2Left);doubledRight=closestPairRec(xMid,xEnd,midIt,PyEnd,aux,best1Right,best2Right);doubled;if(dLeft<dRight){d=dLeft;best1=best1Left;best2=best2Left;}else{d=dRight;best1=best1Right;best2=best2Right;}// Erzeuge den Streifen: Durchlaufe den gesamten Py-Bereich und wähle Punkte,// deren Abstand zur vertikalen Linie (midPoint.x) kleiner als d ist.aux.clear();for(autoit=PyBegin;it!=PyEnd;++it){if(abs(it->x-midPoint.x)<d){aux.push_back(*it);}}Pointbest1Strip,best2Strip;doubledStrip=stripClosest(aux.begin(),aux.end(),d,best1Strip,best2Strip);if(dStrip<d){d=dStrip;best1=best1Strip;best2=best2Strip;}returnd;}// Diese Funktion bereitet die sortierten Vektoren vor und ruft den rekursiven Algorithmus auf.doubleclosestPair(vector<Point>&points,Point&best1,Point&best2){intn=points.size();vector<Point>Px=points;// Punkte sortiert nach xvector<Point>Py=points;// Punkte sortiert nach ysort(Px.begin(),Px.end(),[](constPoint&a,constPoint&b){returna.x<b.x;});sort(Py.begin(),Py.end(),[](constPoint&a,constPoint&b){returna.y<b.y;});vector<Point>aux;aux.reserve(n);// Reserviere O(n) Speicher für den HilfsvektorreturnclosestPairRec(Px.begin(),Px.end(),Py.begin(),Py.end(),aux,best1,best2);}intmain(){vector<Point>points={{2,3},{12,30},{40,50},{5,1},{12,10},{3,4}};Pointbest1,best2;doubledist=closestPair(points,best1,best2);cout<<"Kleinste Distanz: "<<dist<<"\n";cout<<"Nächstgelegenes Paar: "<<best1<<" und "<<best2<<"\n";return0;}
Randomisierter Algorithmus
Funktionsweise
Der randomisierte Algorithmus beruht darauf, dass die Punkte in zufälliger Reihenfolge in eine Hashtabelle eingefügt werden, wobei das Einfügen eines Punktes ebenso wie das Testen, ob er den vorher bekannten minimalen Abstand unterbietet, konstante Laufzeit hat. Die Hashtabelle bildet alle Gitterzellen der Größe auf den eventuell darin liegenden Punkt ab. Es muss zwar bei jeder solchen Aktualisierung von die Hashtabelle neu aufgebaut werden, die Gesamtzahl der Einfügeoperationen in sämtliche dieser Hashtabellen liegt aber trotzdem in . Ohne Beachtung, wie die Hashtabelle konkret genutzt wird, ergibt sich folgender Algorithmus:
Bilde eine zufällige Reihenfolge der Punkte P1, ..., Pn
δ = Abstand zwischen P1 und P2
Füge P1 und P2 in die Hashtabelle ein (Gittergröße δ/2)
Für i = 3, ..., n
Falls Pi einen Abstand δ < δ' zu einem der Punkte P1, ..., Pi-1 hat:
δ = δ'
Baue die Hashtabelle neu auf (Gittergröße δ'/2)
Füge Pi in die Hashtabelle ein
Gitterstruktur und Hashfunktion
Man kann vereinfachend annehmen, dass alle Punkte Koordinaten zwischen (0,0) und (1,1) haben. Gegebenenfalls kann hierfür eine Koordinatentransformation vorgenommen werden. Die Ebene, in der die Punkte liegen, wird sodann durch ein Gitter der Weite überdeckt. Es wird nun eine universelle Hashfunktion benötigt; sie ermöglicht es einerseits, von einer gegebenen Gitterzelle in konstanter Laufzeit festzustellen, ob sich in dieser Gitterzelle einer der Punkte befindet, und andererseits neue Punkte in konstanter Laufzeit in die bestehende Hashtabelle einzufügen.
Bei jedem Einfügen eines neuen Punktes P ist zu prüfen, ob dadurch der bereits bekannte minimale Abstand unterschritten wird. Anhand der Koordinaten von P lässt sich sofort bestimmen, in welcher der Gitterzellen der Punkt P liegt. Aufgrund der Aufteilung in Gitterzellen der Größe muss nur festgestellt werden, ob in einer der umliegenden Gitterzellen schon ein Punkt liegt. Umliegend sind dabei alle Gitterzellen, die einen solchen Abstand begründen könnten, also nur das umgebende 5x5-Rechteck. Alle Punkte, die außerhalb dieses Bereichs liegen, können keinen kleineren Abstand zu P haben, weil sie aufgrund der Gittergröße mindestens den Abstand haben. Weil in jeder Gitterzelle nur ein einziger Punkt liegen kann, denn sonst wäre vorher bereits ein kleinerer Abstand gefunden worden, müssen also auch nur höchstens 25 Punkte geprüft werden. Sofern in diesem Bereich keine Punkte liegen, kann P bedenkenlos in die bestehende Hashtabelle eingefügt werden.
Sind jedoch in diesem Bereich bereits weitere Punkte vorhanden, so wird der Abstand auf den neu gefundenen minimalen Abstand gesetzt. Dies hat zur Folge, dass die bisherige Hashtabelle nutzlos geworden ist, weil ihre Gitterweite nicht mehr mit übereinstimmt. Wir benötigen also eine neue Hashtabelle mit korrektem . Wir erstellen einfach eine solche Tabelle für alle Punkte bis zu P von Grund auf neu. Der Effizienz halber kann man sich beim Neuerstellen die Abstandsprüfung sparen, weil der minimale Abstand aller Punkte zu P bereits bekannt ist.
Schließlich hat man nach dem Einfügen eines neuen Punktes immer eine korrekte Hashtabelle mit Gitterweite und in jedem Gitterquadrat liegt höchstens ein Punkt.
Laufzeit
Relevant für die Laufzeit ist lediglich die Anzahl der Einfüge-Operationen neuer Punkte. Trivialerweise muss jeder Punkt mindestens einmal eingefügt werden, also ist die Laufzeit mindestens linear.
Fraglich ist jedoch, wie sich der gelegentlich vorkommende Neuaufbau der Hashtabelle auswirkt, weil hierfür alle bereits bekannten Punkte erneut eingefügt werden müssen. Hierfür ist es notwendig, die Wahrscheinlichkeit anzugeben, mit der beim Einfügen eines Punkts ein Neuaufbau erforderlich wird, und die dafür notwendigen Kosten einzuberechnen. Intuitiv sieht man, dass mit zunehmender Anzahl der Punkte der Aufwand für die Reorganisation immer größer wird, die Wahrscheinlichkeit dafür aber immer kleiner.
Die Wahrscheinlichkeit dafür, dass der Punkt beim Einfügen einen kleineren Abstand erzeugt, ist gleich , denn dafür müsste einer der 2 Punkte sein, die diesen Abstand zueinander haben.
Definieren wir nun die Zufallsvariable . Sie sei 1, falls beim Einfügen des Punkts ein kleinerer Abstand entsteht, und sonst 0.
Dann gilt: Die Gesamtanzahl der Einfügeoperationen ist , also die Anzahl der eingefügten Punkte plus die Anzahl der Einfügeoperationen durch die Neuorganisationen der Hashtabelle.
Das heißt, die Gesamtanzahl der erwarteten Einfügeoperationen ist lediglich gleich . Insgesamt ist die erwartete Laufzeit somit linear, liegt also in .
Größter Abstand von Punkten in der Ebene
Rotating calipers Algorithmus
Der Rotating calipers Algorithmus ist danach benannt, dass ein Messschieber um die Außenseite eines konvexen Polygons gedreht wird. Jedes Mal, wenn ein Messschenkel flach an einer Seite des Polygons liegt, bildet es ein antipodales Punktpaar, wobei der Punkt oder die Seite den entgegengesetzte Messschenkel berührt. Die vollständige Drehung des Messschiebers um das Polygon erkennt alle antipodalen Punktpaare und bestimmt das Punktepaar mit dem größten Abstand.
Um den Algorithmus auf beliebige Punkte in der Ebene anwenden zu können, muss erst die konvexe Hülle der Punkte bestimmt werden. Dafür wird die Laufzeit benötigt.
Zwei Punkte mit maximalem Abstand müssen auf den Rand des konvexen Polygons liegen, das die konvexe Hülle bildet. Der Algorithmus beginnt mit den Punkten und und berechnet den Flächeninhalt der Dreiecke . Zunächst wird gesetzt und so lange erhöht, wie der Flächeninhalt zunimmt. So wird der antipodalen Punkt für bestimmt. Auf ähnliche Weise wird der antipodale Punkt für bestimmt wird, indem der Flächeninhalt der Dreiecke berechnet wird und der Index weiter erhöht wird, so lange der Flächeninhalt zunimmt. Diese Schritte werden mit den anderen Punkten wiederholt, bis der Index erreicht ist, also ist. Die Abstände der antipodalen Punktpaare werden berechnet und mit dem größten bisher gefundenen Abstand verglichen. Die Laufzeit für die Berechnung der Flächeninhalte und Abstände liegt in .
Das folgende Computerprogramm in der ProgrammierspracheC++ zeigt eine Implementierung des Rotating calipers Algorithmus. Bei der Ausführung des Programms wird die Methodemain verwendet, die das Punktepaar und den kleinsten Abstand zwischen dem Punktpaar das auf der Konsole ausgibt.[2]
#include<iostream>#include<sstream>#include<stack>#include<vector>usingnamespacestd;// Deklariert den Datentyp für Punkte mit x-Koordinate und y-Koordinate in der EbenestructPoint{intx,y;};PointlowestPoint;// Globale Variable für den Punkt mit der kleinsten y-Koordinate// Diese Funktion berechnet das Doppelte des Flächeninhalts des Dreiecks aus den drei Punkten. Wenn die Punkte im Uhrzeigersinn sind, ist das Vorzeichen positiv. Wenn die Punkte im Gegenuhrzeigersinn sind, ist das Vorzeichen negativ.intgetArea(Pointpoint1,Pointpoint2,Pointpoint3){return((point1.y-point2.y)*(point2.x-point3.x)-(point2.y-point3.y)*(point1.x-point2.x));}// Diese Funktion bestimmt die Orientierung des drei Punkte. Wenn die Punkte im Uhrzeigersinn sind, wird der Wert 1 zurückgegeben. Wenn die Punkte im Gegenuhrzeigersinn sind, wird der Wert -1 zurückgegeben. Wenn die Punkte kollinear sind, wird der Wert 0 zurückgegeben.intgetOrientation(Pointpoint1,Pointpoint2,Pointpoint3){intarea=getArea(point1,point2,point3);// Aufruf der Funktion, die das Doppelte des Flächeninhalts mit Vorzeichen berechnetif(area>0)// Wenn das Vorzeichen positiv ist, sind die Punkte im Uhrzeigersinn{return1;}if(area<0)// Wenn das Vorzeichen negativ ist, sind die Punkte im Gegenuhrzeigersinn{return-1;}return0;// Wenn der Flächeninhalt gleich 0 ist, sind die Punkte kollinear}// Diese Funktion berechnet das Doppelte des Flächeninhalts des Dreiecks aus den drei PunktenintabsoluteArea(Pointpoint1,Pointpoint2,Pointpoint3){returnabs(getArea(point1,point2,point3));// Aufruf der Funktion, die das Doppelte des Flächeninhalts mit Vorzeichen berechnet}// Diese Funktion berechnet den euklidischen Abstand zwischen zwei PunktendoublegetDistance(Pointpoint1,Pointpoint2){returnsqrt((point1.x-point2.x)*(point1.x-point2.x)+(point1.y-point2.y)*(point1.y-point2.y));}// Vergleichsfunktion für das Sortieren der Punkte nach Orientierung im UhrzeigersinnintcompareByOrientation(constvoid*a,constvoid*b){Point*point1=(Point*)a,*point2=(Point*)b;intorientation=getOrientation(lowestPoint,*point1,*point2);// Aufruf der Funktion, die die Orientierung der Punkte bestimmtif(orientation==0)// Wenn die Punkte kollinear sind, werden die Abstände zum Referenzpunkt verglichen{return(int)(getDistance(*point1,lowestPoint)-getDistance(*point2,lowestPoint));}if(orientation==1)// Wenn die Punkte im Uhrzeigersinn sind, ist der erste Punkt größer{return1;}return-1;// Wenn die Punkte im Gegenuhrzeigersinn sind, ist der zweite Punkt größer}// Diese Funktion bestimmt die konvexe Hülle der gegebenen Punkte mit dem Algorithmus Graham Scanvector<Point>getConvexHull(Pointpoints[],intnumberOfPoints){// Bestimmt den Punkt mit der kleinsten y-Koordinate und seinen Index. Wenn die y-Koordinate gleich ist, wird der Punkt mit der Punkt mit der kleinsten x-Koordinate bestimmt.lowestPoint={INT_MAX,INT_MAX};intindex=-1;for(inti=0;i<numberOfPoints;i++)// for-Schleife, die alle Punkte durchläuft{if(points[i].y<lowestPoint.y)// Vergleicht die y-Koordinate der Punkte{lowestPoint.y=points[i].y;lowestPoint.x=points[i].x;index=i;}else{if(lowestPoint.y==points[i].y&&points[i].x<lowestPoint.x)// Vergleicht die x-Koordinate der Punkte, wenn die y-Koordinate gleich ist{lowestPoint.x=points[i].x;index=i;}}}points[index]=points[0];points[0]=lowestPoint;// Setzt den Punkt an den Anfang des Arraysqsort(points,numberOfPoints,sizeof(Point),compareByOrientation);// Sortiert die Punkte nach Orientierung im Uhrzeigersinnstack<Point>pointStack;// Stack für die konvexe HüllepointStack.push(points[0]);// Fügt den ersten Punkt der konvexen Hülle hinzuintk=1;while(k<numberOfPoints-1&&getOrientation(points[0],points[k],points[k+1])==0)// Bestimmt den nächsten Punkt, der nicht kollinear ist{k++;}pointStack.push(points[k]);// Fügt den zweiten Punkt der konvexen Hülle hinzufor(inti=k+1;i<numberOfPoints;i++)// for-Schleife, die alle weiteren Punkte des Stack durchläuft und die konvexe Hülle erzeugt{Pointpoint=pointStack.top();pointStack.pop();// Entfernt das oberste Element vom Stackwhile(getOrientation(pointStack.top(),point,points[i])>=0)// Bestimmt den nächsten Punkt, der mit den beiden obersten Elementen des Stack im Uhrzeigersinn ist{point=pointStack.top();pointStack.pop();// Entfernt das oberste Element vom Stack}pointStack.push(point);pointStack.push(points[i]);// Fügt den nächsten Punkt dem Stack hinzu}vector<Point>convexHull;// Vektor für die konvexe Hülleintn=pointStack.size();for(inti=0;i<n;i++)// for-Schleife, die alle Punkte des Stack durchläuft und dem Vektor hinzufügt{convexHull.push_back(pointStack.top());// Fügt das oberste Element des Stack dem Vektor hinzupointStack.pop();}returnconvexHull;}// Diese Funktion gibt den größten Abstand der Punkte und Referenzen auf die zwei Punkte mit dem kleinsten Abstand zurück und verwendet den Rotating calipers AlgorithmusdoublegetLargestDistance(Pointpoints[],Point&point1,Point&point2,intnumberOfPoints){vector<Point>convexHull=getConvexHull(points,numberOfPoints);// Aufruf der Funktion für die konvexe HüllePoint*hull=newPoint[convexHull.size()];// Referenz auf das Array für die konvexe Hülleintn=convexHull.size();// Variable für die Anzahl der Punkte der konvexen Hüllefor(inti=0;i<n;i++)// for-Schleife, die das Array für die konvexe Hülle füllt{hull[i]=convexHull[i];}if(n==1)// Wenn die konvexe Hülle aus 1 Punkt besteht, ist der größte Abstand gleich 0{return0;}if(n==2)// Wenn die konvexe Hülle aus 2 Punkten besteht, ist der größte Abstand der Abstand dieser 2 Punkte{returnsqrt(getDistance(hull[0],hull[1]));}intk=1;// Variable für den Index des ersten Punktswhile(absoluteArea(hull[n-1],hull[0],hull[(k+1)%n])>absoluteArea(hull[n-1],hull[0],hull[k]))// while-Schleife, die den Punkt mit dem größte Abstand zum ersten und letzten Punkt der konvexe Hülle bestimmt{k++;}doublemaximumDistance=0;inti=0;intj=k;// Bestimmt die zwei Punkte der konvexe Hülle mit dem größten Abstand mit dem Rotating calipers Algorithmuswhile(i<=k&&j<n)// while-Schleife, die den Index des ersten Punkts durchläuft{doublecurrentDistance=getDistance(hull[i],hull[j]);// Ruft die Funktion für die Berechnung des euklidischen Abstands aufif(currentDistance>maximumDistance)// Prüft, ob der Abstand zwischen den aktuellen Punkten größer als der bisher größte Abstand ist{point1=hull[i];point2=hull[j];maximumDistance=currentDistance;}while(j<n&&absoluteArea(hull[i],hull[(i+1)%n],hull[(j+1)%n])>absoluteArea(hull[i],hull[(i+1)%n],hull[j]))// while-Schleife, die den Index des zweiten Punkts durchläuft{doublecurrentDistance=getDistance(hull[i],hull[(j+1)%n]);// Ruft die Funktion für die Berechnung des euklidischen Abstands aufif(currentDistance>maximumDistance)// Prüft, ob der Abstand zwischen den aktuellen Punkten größer als der bisher größte Abstand ist{point1=hull[i];point2=hull[(j+1)%n];maximumDistance=currentDistance;}j++;}i++;}returnmaximumDistance;}// Diese Funktion gibt den Punkt in der Form (x, y) zurückstringpointToString(Point*point){stringstreamtext;text<<"("<<point->x<<", "<<point->y<<")"<<endl;returntext.str();// Typumwandlung von stringstream nach string}// Hauptfunktion die das Programm ausführtintmain(){Pointpoints[]={{4,0},{0,2},{-1,-7},{1,10},{2,-3}};// Deklariert und initialisiert ein Array mit 5 PunktenintnumberOfPoints=sizeof(points)/sizeof(points[0]);Pointpoint1,point2;// Deklariert Referenzen für die zwei Punkte mit dem größten Abstandcout<<"Der größte Abstand zwischen zwei Punkten ist "<<getLargestDistance(points,point1,point2,numberOfPoints)<<endl;// Aufruf der Funktion, Ausgabe auf der Konsolecout<<"Erster Punkt: "<<pointToString(&point1);// Ausgabe auf der Konsolecout<<"Zweiter Punkt: "<<pointToString(&point2);// Ausgabe auf der Konsole}
Literatur
Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, und Clifford Stein. Algorithmen – Eine Einführung. Oldenbourg-Verlag, 2004. ISBN 3-486-27515-1. Seiten 959–963 (Kapitel 33.4: Berechnung des dichtesten Punktepaares).
Jon Kleinberg, Éva Tardos. Algorithm Design. Pearson International Edition, 2006. ISBN 0-321-37291-3. Seiten 225ff (Divide & Conquer); Seiten 741ff (Randomisierter Algorithmus).