Effiziente Reihe Schnittpunkt einer Sammlung von Sätzen in C++
Habe ich eine Sammlung von std::set
. Ich möchte zu finden, die Schnittmenge aller sets in dieser Sammlung, in der schnellsten Art und Weise. Die Anzahl der Sätze in der Sammlung ist in der Regel sehr klein (~5-10), und die Anzahl der Elemente in jedem Satz ist in der Regel weniger als 1000, kann aber gelegentlich gehen bis etwa 10000. Aber ich brauche zu tun diese Kreuzungen Zehntausende von Zeit, so schnell wie möglich. Ich versuchte benchmark ein paar Methoden wie folgt:
- In-place-Kreuzung in eine
std::set
Objekt, das zunächst kopiert den ersten Satz. Dann für den folgenden Sätzen, es iteriert über alle element der selbst-und der I-TEN Reihe von der Auflistung und entfernt Elemente aus sich selbst, wie benötigt werden. - Mit
std::set_intersection
in eine temporärestd::set
-, swap-Inhalte zu einem aktuellen Satz, dann wieder suchen Schnittpunkt der aktuelle Satz mit dem nächsten Satz und einfügen in die temp einstellen, und so weiter. - Manuell iterieren über alle Elemente aller Mengen, wie in 1), aber mit einem
vector
als Ziel-container stattstd::set
. - Gleichen wie in 4, aber mit einem
std::list
statt einervector
den Verdacht, dass einlist
wird eine schnellere Löschungen aus der Mitte. - Mit hash-sets (
std::unordered_set
) und überprüfen Sie für alle Elemente in allen sets.
Wie sich herausstellte, mit einem vector
ist geringfügig schneller, wenn die Anzahl der Elemente in jeder Gruppe ist klein, und list
ist geringfügig schneller für größere sets. In-place-mithilfe set
ist wesentlich langsamer als die beiden, gefolgt von set_intersection
- und hash-sets. Gibt es einen schnelleren Algorithmus/datastructure/tricks das zu erreichen? Ich kann nach code-snippets, falls erforderlich. Danke!
- Die Frage hängt wirklich davon ab, ob oder nicht von Ihnen erwartet, finden viele gemeinsame Elemente, oder nicht, wie dies verändert die "beste" Struktur, die man mit oben kommen kann. Zum Beispiel, ein 6. Methode ist einfach zu benutzen und
std::unordered_map
und zählen der Anzahl der vorkommen der einzelnen Elemente. Es ist O(N) und die Gesamtzahl der Elemente. Sie dann, wählen Sie nur die Elemente, die insgesamt gleich der Anzahl der Sätze, O(M) die Anzahl der unterschiedlichen Elemente. Keine Ahnung, wie gut es durchführen würde. - Ich sehe. Ich geben diesem einen Versuch, obwohl ich vermute, es wird nicht schneller sein als ein
std::list
durch das hashing und die sonstigen Gemeinkosten. Danke! - Dieser Methode geben Sie die Ergebnismenge in unsortierter Reihenfolge. Zum Glück habe ich zwei Anwendungsfälle, eines für das Ergebnis in der Reihenfolge sortiert, und eine, die nicht. Wenn diese Methode ist Recht schnell, ich kann es atleast für den Fall, wo die Kreuzung ist nicht nötig, um sortiert werden.
- Ich habe versucht, diesen Ansatz, und für meine Daten, das war nur geringfügig schneller als mein Ansatz 5 (mit
unordered_set
). - Sie könnten versuchen, die Idee. Schlimmsten Fall linear (lässt sich nicht vermeiden, dass, wenn die sets haben meist die gleichen Elemente), aber wenn die Schnittmenge klein ist, kann es viel schneller.
- Danke!!! Aufgrund Dietmar, die Antwort unten, ich hatte auch darüber nachgedacht, eine binäre Suche wenn tut, Suche in arrays. Aber der Schlimmste Fall Verlangsamungen war eine Sorge. Sie schlagen vor, eine sehr schöne Heuristik/Einschätzung zu machen dies zu einem hybrid-Ansatz. In der Tat, dies ist nur geringfügig langsamer als die vector-Ansatz (pt 3 oben) durch kleine zusätzliche Berechnungen, aber klar der Schnellste unter allen, wenn Sie die Größen der nachfolgenden Sätze ist ausreichend größer als das aktuelle! Sehr schöne Idee!
- Ich hätte akzeptiert, wenn es eine Antwort gab.
- könnten wir einen Blick auf die Quelle des Tests?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Möchten Sie vielleicht versuchen, eine Verallgemeinerung der
std::set_intersection()
: der Algorithmus ist die Verwendung von Iteratoren für alle sets:end()
des entsprechenden set sind Sie fertig. Es kann somit davon ausgegangen werden, dass alle Iteratoren sind gültig.x
.std::find_if()
das erste element mindestens so groß, wiex
.x
machen es die neuen Kandidaten Wert und suchen Sie erneut in der Reihenfolge der Iteratoren.x
Sie finden ein element der Schnittmenge: Notieren Sie, erhöhen Sie alle Iteratoren, starten Sie vorbei.std::find_if
wenn man die Arbeit mitstd::set
schließlichstd::set
verfügt sowohlstd::lower_bound
undstd::upper_bound
sind in der Regel schneller.find_if
im Durchschnitt müssen nie vorher mehr als zwei Elementen und ist somit O (1), während???er_bound
ist O (log n).std::set_intersection()
als auch nicht. Interessanterweise, ich denke, die Komplexität der vorgeschlagene Ansatz ist O((n log n) * m) : won
ist die maximale Größe des sets undm
ist die Anzahl der Sätze. Mein Algorithmus hat Komplexität O(n * m). Ich denke, dass meine Vorgehensweise gewinnt.find_if
im Durchschnitt müssen nie vorher mehr als zwei Elemente?x
können wir halten Sie suchen und behalten den größten Wert gefunden, bis zum Ende, und machen, dass die neuex
. Habe ich Recht dazu?lower_bound
benutzt binäre Suche. Aber der exponent sieht beängstigend aus! 🙂 Ich denke, wenn ich dies umzusetzen, werde ich versuchen mit den beidenfind_if
undlower_bound
.find_if
wäre schneller. Ich bin versucht, es zu ändern, verwendenfind_if
stattlower_bound
, aber am stuck zu machen, einen Komparator für die Prüfung mit dem derzeit besten.std::set_intersection()
. Erstellen Sie ein geeignetes Prädikat, Sie sollten in der Lage sein zu verwendenstd::bind1st(std::less_equal<T>(), x)
.find_if
ich manuell inkrementiert den iterator bis zum entsprechenden Wert (ich denkefind_if
tut genau dies). Auf diese Weise, die Zeit gebracht wurde deutlich. Es ist jetzt schneller als die wiederholteset_intersection
, aber immer noch langsamer als die beiden schnellsten (3 und 4 in Frage). Alles, was ich Tat, war zu änderniterators[i] = lower_bound(iterators[i], sets[i].end(), currentValue, comparator);
zuwhile (iterators[i] != sets[i].end() && *(*iterators[i]) > *currentValue) ++iterators[i];
das ist im Grunde ersetzenlower_bound
durchfind_if
x
größer als der aktuelle besten, wir erlöschen alle vorherigen Prüfungen, und für die nachfolgenden Iterationen, die Suche für diese neuex
. Am Ende der Liste, die wir wieder von vorne anfangen, da Sie ungültig geworden sind aufgrund der aktuellenx
größer als das, was überprüft wurde, auf Sie. Dies führt zu großen Sprüngen (es sind die kleinen Sprünge, die Probleme verursacht für die log n lower_bound). Die weitere Verbesserung könnte sein, die Sie vorgeschlagen: um die sets (noch nicht gemacht, dass in diesem)lower_bound
Ansatz war, weil ich war mit dem global/algorithmenlower_bound
. Wenn ich eingeschaltet, um diestd::set::lower_bound
der Zeit sank drastisch, die vergleichbar mit wiederholtenset_intersect
. Allerdings war es noch nicht schnell genug, als das lineare Inkrement ähnlichfind_if
wie oben beschrieben. Ich nehme an, die zweilower_bound
Funktionen verwenden iterator anders (random vs vorwärts), oder eine solche Gründen.lower_bound
(binäre Suche) Ansatz ist schneller alsfind_if
(linear), wenn die Anzahl der Kreuzungen sind kleiner, aber langsamer, wenn die Anzahl der Kreuzungen ist groß. Ich denke, das ist zu erwarten. Insgesamt, diese sind nah, aber langsamer als die Ansätze 3 und 4 in Frage, aber viel schneller als die anderen Ansätze aufgeführt.Nacht ist ein guter Berater und ich denke, vielleicht habe ich eine Idee 😉
Dies ist der Grund, warum, wo Geschwindigkeiten Sache, ein
vector
(oder vielleicht eindeque
) sind so große Strukturen: Sie spielen sehr gut mit der Erinnerung. Als solche, ich würde definitiv empfehlen die Verwendung vonvector
als unseren Vermittler-Strukturen; obwohl Pflege müssen getroffen werden, um immer nur einfügen/löschen von einer Extremität zu vermeiden Umzug.Also dachte ich über einen Recht einfachen Ansatz:
Scheint es richtig, kann ich nicht garantieren, seine Geschwindigkeit, obwohl, offensichtlich.
vector
als einen Zwischenbehälter, so wie Sie das getan haben. Der Unterschied ist, dass Sie verwendet dieset_intersection
, die erfordert zweivectors
, während ich gehalten 1 Vektor, mit dem Nachteil, dass musste ich löschen von der Mitte. Auch wenn Ihr Ansatz sollte idealerweise schneller gewesen, ich denke, die Komplex zusammenhängenden Faktoren wie Speicher, caching - (1-array vs 2) etc machen diese langsamer als die Optionen 3 und 4, die ich versuchte oben. Natürlich, Kilometerstand kann variieren, basierend auf den Daten.