Ein Punkt in 3-d wird durch (x, y, z) definiert. Der Abstand d zwischen zwei beliebigen Punkten (X, Y, Z) und (x, y, z) beträgt d = Sqrt [(Xx) ^ 2 + (Yy) ^ 2 + (Zz) ^ 2]. Jetzt gibt es eine Million Einträge in einer Datei, jeder Eintrag ist ein Punkt im Raum, in keiner bestimmten Reihenfolge. Wenn ein Punkt (a, b, c) gegeben ist, finden Sie die nächsten 10 Punkte. Wie würden Sie die Millionen Punkte speichern und wie würden Sie diese 10 Punkte aus dieser Datenstruktur abrufen.
68
Antworten:
Millionen Punkte sind eine kleine Zahl. Der einfachste Ansatz funktioniert hier (Code, der auf KDTree basiert, ist langsamer (zum Abfragen nur eines Punktes)).
Brute-Force-Ansatz (Zeit ~ 1 Sekunde)
#!/usr/bin/env python import numpy NDIM = 3 # number of dimensions # read points into array a = numpy.fromfile('million_3D_points.txt', sep=' ') a.shape = a.size / NDIM, NDIM point = numpy.random.uniform(0, 100, NDIM) # choose random point print 'point:', point d = ((a-point)**2).sum(axis=1) # compute distances ndx = d.argsort() # indirect sort # print 10 nearest points to the chosen one import pprint pprint.pprint(zip(a[ndx[:10]], d[ndx[:10]]))
Starte es:
Hier ist das Skript, das Millionen 3D-Punkte generiert:
Ausgabe:
Mit diesem Code können Sie komplexere Datenstrukturen und Algorithmen testen (z. B. ob sie tatsächlich weniger Speicher verbrauchen oder schneller als der oben einfachste Ansatz). Es ist erwähnenswert, dass es im Moment die einzige Antwort ist, die Arbeitscode enthält.
Lösung basierend auf KDTree (Zeit ~ 1,4 Sekunden)
#!/usr/bin/env python import numpy NDIM = 3 # number of dimensions # read points into array a = numpy.fromfile('million_3D_points.txt', sep=' ') a.shape = a.size / NDIM, NDIM point = [ 69.06310224, 2.23409409, 50.41979143] # use the same point as above print 'point:', point from scipy.spatial import KDTree # find 10 nearest points tree = KDTree(a, leafsize=a.shape[0]+1) distances, ndx = tree.query([point], k=10) # print 10 nearest points to the chosen one print a[ndx]
Starte es:
Teilweise Sortierung in C ++ (Zeit ~ 1,1 Sekunden)
// $ g++ nearest.cc && (time ./a.out < million_3D_points.txt ) #include <algorithm> #include <iostream> #include <vector> #include <boost/lambda/lambda.hpp> // _1 #include <boost/lambda/bind.hpp> // bind() #include <boost/tuple/tuple_io.hpp> namespace { typedef double coord_t; typedef boost::tuple<coord_t,coord_t,coord_t> point_t; coord_t distance_sq(const point_t& a, const point_t& b) { // or boost::geometry::distance coord_t x = a.get<0>() - b.get<0>(); coord_t y = a.get<1>() - b.get<1>(); coord_t z = a.get<2>() - b.get<2>(); return x*x + y*y + z*z; } } int main() { using namespace std; using namespace boost::lambda; // _1, _2, bind() // read array from stdin vector<point_t> points; cin.exceptions(ios::badbit); // throw exception on bad input while(cin) { coord_t x,y,z; cin >> x >> y >> z; points.push_back(boost::make_tuple(x,y,z)); } // use point value from previous examples point_t point(69.06310224, 2.23409409, 50.41979143); cout << "point: " << point << endl; // 1.14s // find 10 nearest points using partial_sort() // Complexity: O(N)*log(m) comparisons (O(N)*log(N) worst case for the implementation) const size_t m = 10; partial_sort(points.begin(), points.begin() + m, points.end(), bind(less<coord_t>(), // compare by distance to the point bind(distance_sq, _1, point), bind(distance_sq, _2, point))); for_each(points.begin(), points.begin() + m, cout << _1 << "\n"); // 1.16s }
Starte es:
Prioritätswarteschlange in C ++ (Zeit ~ 1,2 Sekunden)
#include <algorithm> // make_heap #include <functional> // binary_function<> #include <iostream> #include <boost/range.hpp> // boost::begin(), boost::end() #include <boost/tr1/tuple.hpp> // get<>, tuple<>, cout << namespace { typedef double coord_t; typedef std::tr1::tuple<coord_t,coord_t,coord_t> point_t; // calculate distance (squared) between points `a` & `b` coord_t distance_sq(const point_t& a, const point_t& b) { // boost::geometry::distance() squared using std::tr1::get; coord_t x = get<0>(a) - get<0>(b); coord_t y = get<1>(a) - get<1>(b); coord_t z = get<2>(a) - get<2>(b); return x*x + y*y + z*z; } // read from input stream `in` to the point `point_out` std::istream& getpoint(std::istream& in, point_t& point_out) { using std::tr1::get; return (in >> get<0>(point_out) >> get<1>(point_out) >> get<2>(point_out)); } // Adaptable binary predicate that defines whether the first // argument is nearer than the second one to given reference point template<class T> class less_distance : public std::binary_function<T, T, bool> { const T& point; public: less_distance(const T& reference_point) : point(reference_point) {} bool operator () (const T& a, const T& b) const { return distance_sq(a, point) < distance_sq(b, point); } }; } int main() { using namespace std; // use point value from previous examples point_t point(69.06310224, 2.23409409, 50.41979143); cout << "point: " << point << endl; const size_t nneighbours = 10; // number of nearest neighbours to find point_t points[nneighbours+1]; // populate `points` for (size_t i = 0; getpoint(cin, points[i]) && i < nneighbours; ++i) ; less_distance<point_t> less_distance_point(point); make_heap (boost::begin(points), boost::end(points), less_distance_point); // Complexity: O(N*log(m)) while(getpoint(cin, points[nneighbours])) { // add points[-1] to the heap; O(log(m)) push_heap(boost::begin(points), boost::end(points), less_distance_point); // remove (move to last position) the most distant from the // `point` point; O(log(m)) pop_heap (boost::begin(points), boost::end(points), less_distance_point); } // print results push_heap (boost::begin(points), boost::end(points), less_distance_point); // O(m*log(m)) sort_heap (boost::begin(points), boost::end(points), less_distance_point); for (size_t i = 0; i < nneighbours; ++i) { cout << points[i] << ' ' << distance_sq(points[i], point) << '\n'; } }
Starte es:
Linearer suchbasierter Ansatz (Zeit ~ 1,15 Sekunden)
// $ g++ -O3 nearest.cc && (time ./a.out < million_3D_points.txt ) #include <algorithm> // sort #include <functional> // binary_function<> #include <iostream> #include <boost/foreach.hpp> #include <boost/range.hpp> // begin(), end() #include <boost/tr1/tuple.hpp> // get<>, tuple<>, cout << #define foreach BOOST_FOREACH namespace { typedef double coord_t; typedef std::tr1::tuple<coord_t,coord_t,coord_t> point_t; // calculate distance (squared) between points `a` & `b` coord_t distance_sq(const point_t& a, const point_t& b); // read from input stream `in` to the point `point_out` std::istream& getpoint(std::istream& in, point_t& point_out); // Adaptable binary predicate that defines whether the first // argument is nearer than the second one to given reference point class less_distance : public std::binary_function<point_t, point_t, bool> { const point_t& point; public: explicit less_distance(const point_t& reference_point) : point(reference_point) {} bool operator () (const point_t& a, const point_t& b) const { return distance_sq(a, point) < distance_sq(b, point); } }; } int main() { using namespace std; // use point value from previous examples point_t point(69.06310224, 2.23409409, 50.41979143); cout << "point: " << point << endl; less_distance nearer(point); const size_t nneighbours = 10; // number of nearest neighbours to find point_t points[nneighbours]; // populate `points` foreach (point_t& p, points) if (! getpoint(cin, p)) break; // Complexity: O(N*m) point_t current_point; while(cin) { getpoint(cin, current_point); //NOTE: `cin` fails after the last //point, so one can't lift it up to //the while condition // move to the last position the most distant from the // `point` point; O(m) foreach (point_t& p, points) if (nearer(current_point, p)) // found point that is nearer to the `point` //NOTE: could use insert (on sorted sequence) & break instead //of swap but in that case it might be better to use //heap-based algorithm altogether std::swap(current_point, p); } // print results; O(m*log(m)) sort(boost::begin(points), boost::end(points), nearer); foreach (point_t p, points) cout << p << ' ' << distance_sq(p, point) << '\n'; } namespace { coord_t distance_sq(const point_t& a, const point_t& b) { // boost::geometry::distance() squared using std::tr1::get; coord_t x = get<0>(a) - get<0>(b); coord_t y = get<1>(a) - get<1>(b); coord_t z = get<2>(a) - get<2>(b); return x*x + y*y + z*z; } std::istream& getpoint(std::istream& in, point_t& point_out) { using std::tr1::get; return (in >> get<0>(point_out) >> get<1>(point_out) >> get<2>(point_out)); } }
Messungen zeigen, dass die meiste Zeit damit verbracht wird, Arrays aus der Datei zu lesen, tatsächliche Berechnungen in der Größenordnung weniger Zeit in Anspruch nehmen.
quelle
Wenn sich die Millionen Einträge bereits in einer Datei befinden, müssen sie nicht alle in eine Datenstruktur im Speicher geladen werden. Behalten Sie einfach ein Array mit den bisher gefundenen Top-Ten-Punkten bei, scannen Sie die Millionen Punkte und aktualisieren Sie dabei Ihre Top-Ten-Liste.
Dies ist O (n) in der Anzahl der Punkte.
quelle
Sie können die Punkte in einem k-dimensionalen Baum (kd-Baum) speichern . Kd-Bäume sind für die Suche nach dem nächsten Nachbarn optimiert (Finden der n Punkte, die einem bestimmten Punkt am nächsten liegen).
quelle
Ich denke, dies ist eine knifflige Frage, die prüft, ob Sie nicht versuchen, Dinge zu übertreiben.
Betrachten Sie den einfachsten Algorithmus, den die Leute bereits oben angegeben haben: Führen Sie eine Tabelle mit zehn bisher besten Kandidaten und gehen Sie alle Punkte nacheinander durch. Wenn Sie einen näheren Punkt als einen der zehn bisher besten finden, ersetzen Sie ihn. Was ist die Komplexität? Nun, wir müssen jeden Punkt aus der Datei einmal betrachten , seine Entfernung (oder das Quadrat der tatsächlichen Entfernung) berechnen und mit dem 10. nächstgelegenen Punkt vergleichen. Wenn es besser ist, fügen Sie es an der entsprechenden Stelle in die Tabelle mit den 10 bisher besten ein.
Wie komplex ist das? Wir betrachten jeden Punkt einmal, also sind es n Berechnungen der Entfernung und n Vergleiche. Wenn der Punkt besser ist, müssen wir ihn an der richtigen Position einfügen. Dies erfordert einige weitere Vergleiche, aber dies ist ein konstanter Faktor, da die Tabelle der besten Kandidaten eine konstante Größe von 10 hat.
Wir erhalten einen Algorithmus, der in linearer Zeit abläuft, O (n) in der Anzahl der Punkte.
Aber jetzt überlegen Sie, was die Untergrenze für einen solchen Algorithmus ist. Wenn es keine Ordnung in den Eingangsdaten ist, wir müssen an jedem Punkt Blick zu sehen , ob es nicht einer der engstenen Einsen ist. Soweit ich sehen kann, ist die Untergrenze Omega (n) und daher ist der obige Algorithmus optimal.
quelle
Die Entfernung muss nicht berechnet werden. Nur das Quadrat der Entfernung sollte Ihren Bedürfnissen entsprechen. Sollte schneller sein, denke ich. Mit anderen Worten, Sie können das
sqrt
Bit überspringen .quelle
Dies ist keine Hausaufgabenfrage, oder? ;-);
Mein Gedanke: Durchlaufen Sie alle Punkte und legen Sie sie in einen minimalen Haufen oder eine begrenzte Prioritätswarteschlange, die durch die Entfernung vom Ziel bestimmt wird.
quelle
Diese Frage testet im Wesentlichen Ihr Wissen und / oder Ihre Intuition über Raumpartitionierungsalgorithmen . Ich würde sagen, dass das Speichern der Daten in einem Octree die beste Wahl ist. Es wird häufig in 3D-Engines verwendet, die genau diese Art von Problem behandeln (Speichern von Millionen von Scheitelpunkten, Raytracing, Auffinden von Kollisionen usw.). Die Suchzeit wird
log(n)
im schlimmsten Fall in der Größenordnung liegen (glaube ich).quelle
Einfacher Algorithmus:
Speichern Sie die Punkte als Liste von Tupeln, scannen Sie die Punkte, berechnen Sie die Entfernung und führen Sie eine Liste mit den nächstgelegenen Punkten.
Kreativer:
Gruppieren Sie Punkte in Regionen (z. B. den durch "0,0,0" bis "50,50,50" oder "0,0,0" bis "-20, -20, -20" beschriebenen Würfel) kann vom Zielpunkt aus in sie "indizieren". Überprüfen Sie, in welchem Würfel das Ziel liegt, und durchsuchen Sie nur die Punkte in diesem Würfel. Wenn dieser Würfel weniger als 10 Punkte enthält, überprüfen Sie die "benachbarten" Würfel und so weiter.
Bei weiterer Überlegung ist dies kein sehr guter Algorithmus: Wenn Ihr Zielpunkt näher an der Wand eines Würfels liegt als 10 Punkte, müssen Sie auch nach dem benachbarten Würfel suchen.
Ich würde mich für den kd-tree-Ansatz entscheiden und den nächstgelegenen Knoten entfernen (oder markieren) und erneut nach dem neuen nächstgelegenen Knoten suchen. Spülen und wiederholen.
quelle
Wenn für zwei beliebige Punkte P1 (x1, y1, z1) und P2 (x2, y2, z2) der Abstand zwischen den Punkten d ist, müssen alle folgenden Bedingungen erfüllt sein:
Halten Sie die 10 am nächsten, während Sie über Ihren gesamten Satz iterieren, aber halten Sie auch den Abstand zur 10. am nächsten. Sparen Sie sich viel Komplexität, indem Sie diese drei Bedingungen verwenden, bevor Sie die Entfernung für jeden Punkt berechnen, den Sie betrachten.
quelle
im Grunde eine Kombination der ersten beiden Antworten über mir. Da sich die Punkte in einer Datei befinden, müssen sie nicht gespeichert werden. Anstelle eines Arrays oder eines Min-Heaps würde ich einen Max-Heap verwenden, da Sie nur nach Entfernungen suchen möchten, die kleiner als der 10. nächstgelegene Punkt sind. Für ein Array müssten Sie jede neu berechnete Entfernung mit allen 10 von Ihnen beibehaltenen Entfernungen vergleichen. Für einen minimalen Haufen müssen Sie 3 Vergleiche mit jeder neu berechneten Entfernung durchführen. Bei einem maximalen Heap führen Sie nur dann einen Vergleich durch, wenn der neu berechnete Abstand größer als der Wurzelknoten ist.
quelle
Berechnen Sie die Entfernung für jeden von ihnen und wählen Sie (1..10, n) in O (n) Zeit. Das wäre der naive Algorithmus, denke ich.
quelle
Diese Frage muss weiter definiert werden.
1) Die Entscheidung bezüglich der Algorithmen, die Daten vorindizieren, variiert sehr stark, je nachdem, ob Sie die gesamten Daten im Speicher halten können oder nicht.
Mit kdtrees und octrees müssen Sie die Daten nicht im Speicher halten, und die Leistung profitiert von dieser Tatsache, nicht nur, weil der Speicherbedarf geringer ist, sondern einfach, weil Sie nicht die gesamte Datei lesen müssen.
Mit bruteforce müssen Sie die gesamte Datei lesen und die Entfernungen für jeden neuen Punkt, nach dem Sie suchen, neu berechnen.
Dies ist jedoch möglicherweise nicht wichtig für Sie.
2) Ein weiterer Faktor ist, wie oft Sie nach einem Punkt suchen müssen.
Wie von JF Sebastian angegeben, ist Bruteforce manchmal sogar bei großen Datenmengen schneller. Beachten Sie jedoch, dass seine Benchmarks das Lesen des gesamten Datensatzes von der Festplatte messen (was nicht erforderlich ist, sobald kd-tree oder octree erstellt und irgendwo geschrieben wurden). und dass sie nur eine Suche messen.
quelle