Warum wird std::map
es als rot-schwarzer Baum implementiert ?
Es gibt mehrere ausgeglichene binäre Suchbäume (BSTs). Was waren Design-Kompromisse bei der Auswahl eines rot-schwarzen Baums?
c++
dictionary
data-structures
stl
binary-search-tree
Denis Gorodetskiy
quelle
quelle
O(logn)
und nichtO(1)
, aber die Werte werden immer sortiert. Ab C ++ 11 (glaube ich) gibt esunordered_map
undunordered_set
, die mit Hash-Funktionen implementiert werden, und obwohl sie nicht sortiert sind, sind die meisten Abfragen und Operationen inO(1)
(durchschnittlich)Antworten:
Wahrscheinlich sind die beiden häufigsten selbstausgleichenden Baumalgorithmen Rot-Schwarz-Bäume und AVL-Bäume . Um den Baum nach dem Einfügen / Aktualisieren auszugleichen, verwenden beide Algorithmen den Begriff der Rotationen, bei denen die Knoten des Baums gedreht werden, um den Neuausgleich durchzuführen.
Während in beiden Algorithmen die Einfüge- / Löschoperationen O (log n) sind, ist im Fall einer Rot-Schwarz-Baum-Neuausgleichsrotation eine O (1) -Operation, während dies bei AVL eine O (log n) -Operation ist, wodurch die Rot-Schwarz-Baum effizienter in diesem Aspekt der Neuausgleichsphase und einer der möglichen Gründe, warum er häufiger verwendet wird.
Rot-Schwarz-Bäume werden in den meisten Sammlungsbibliotheken verwendet, einschließlich der Angebote von Java und Microsoft .NET Framework.
quelle
std::map
Implementierungen zu erstellen , die Entwickler aufzuspüren und sie zu fragen, nach welchen Kriterien sie die Entscheidung getroffen haben. Dies bleibt also Spekulation.Es kommt wirklich auf die Verwendung an. Der AVL-Baum weist normalerweise mehr Rotationen des Ausgleichs auf. Wenn Ihre Anwendung nicht über zu viele Einfüge- und Löschvorgänge verfügt, die Suche jedoch stark belastet, ist der AVL-Baum wahrscheinlich eine gute Wahl.
std::map
Verwendet den Rot-Schwarz-Baum, da ein angemessener Kompromiss zwischen der Geschwindigkeit des Einfügens / Löschens von Knoten und der Suche erzielt wird.quelle
AVL-Bäume haben eine maximale Höhe von 1,44 logn, während RB-Bäume eine maximale Höhe von 2 logn haben. Das Einfügen eines Elements in eine AVL kann eine Neuausrichtung an einer Stelle im Baum bedeuten. Die Neuausrichtung beendet das Einfügen. Nach dem Einfügen eines neuen Blattes muss die Aktualisierung der Vorfahren dieses Blattes bis zur Wurzel oder bis zu einem Punkt erfolgen, an dem die beiden Teilbäume gleich tief sind. Die Wahrscheinlichkeit, k Knoten aktualisieren zu müssen, beträgt 1/3 ^ k. Das Ausbalancieren ist O (1). Das Entfernen eines Elements kann mehr als eine Neuausrichtung bedeuten (bis zur halben Tiefe des Baums).
RB-Bäume sind B-Bäume der Ordnung 4, die als binäre Suchbäume dargestellt werden. Ein 4-Knoten im B-Baum führt zu zwei Ebenen in der äquivalenten BST. Im schlimmsten Fall sind alle Knoten des Baums 2 Knoten mit nur einer Kette von 3 Knoten bis zu einem Blatt. Dieses Blatt befindet sich in einem Abstand von 2 logn von der Wurzel.
Wenn man von der Wurzel zur Einfügemarke hinuntergeht, muss man 4 Knoten in 2 Knoten ändern, um sicherzustellen, dass eine Einfügung ein Blatt nicht sättigt. Nach dem Einfügen müssen alle diese Knoten analysiert werden, um sicherzustellen, dass sie 4 Knoten korrekt darstellen. Dies kann auch im Baum geschehen. Die globalen Kosten sind gleich. Es gibt kein freies Mittagessen! Das Entfernen eines Elements aus dem Baum erfolgt in derselben Reihenfolge.
Alle diese Bäume erfordern, dass Knoten Informationen zu Größe, Gewicht, Farbe usw. enthalten. Nur Splay-Bäume sind frei von solchen zusätzlichen Informationen. Aber die meisten Menschen haben Angst vor Spreizbäumen, weil ihre Struktur so weitläufig ist!
Schließlich können Bäume auch Gewichtsinformationen in den Knoten tragen, was einen Gewichtsausgleich ermöglicht. Es können verschiedene Schemata angewendet werden. Man sollte neu ausbalancieren, wenn ein Teilbaum mehr als die dreifache Anzahl von Elementen des anderen Teilbaums enthält. Das Ausbalancieren erfolgt erneut entweder durch einfache oder doppelte Umdrehung. Dies bedeutet einen Worst-Case von 2,4 logn. Man kann mit 2 statt 3 davonkommen, ein viel besseres Verhältnis, aber es kann bedeuten, dass hier und da etwas weniger als 1% der Teilbäume aus dem Gleichgewicht geraten. Tricky!
Welche Baumart ist die beste? AVL sicher. Sie sind am einfachsten zu codieren und haben ihre schlechteste Höhe, die der Logn am nächsten kommt. Für einen Baum mit 1000000 Elementen hat eine AVL höchstens die Höhe 29, eine RB 40 und ein Gewicht, das je nach Verhältnis zwischen 36 und 50 liegt.
Es gibt viele andere Variablen: Zufälligkeit, Verhältnis von Hinzufügen, Löschen, Suchen usw.
quelle
Die vorherigen Antworten befassen sich nur mit Baumalternativen und Rot-Schwarz bleibt wahrscheinlich nur aus historischen Gründen.
Warum nicht eine Hash-Tabelle?
Für einen Typ muss nur der
<
Operator (Vergleich) als Schlüssel in einem Baum verwendet werden. Für Hash-Tabellen muss jedoch für jeden Schlüsseltyp einehash
Funktion definiert sein. Für eine generische Programmierung ist es sehr wichtig, die Typanforderungen auf ein Minimum zu beschränken, damit Sie sie mit einer Vielzahl von Typen und Algorithmen verwenden können.Das Entwerfen einer guten Hash-Tabelle erfordert genaue Kenntnisse des Kontexts, in dem sie verwendet wird. Sollte es offene Adressierung oder verknüpfte Verkettung verwenden? Welche Belastungsstufen sollte es vor dem Ändern der Größe akzeptieren? Sollte es einen teuren Hash verwenden, der Kollisionen vermeidet, oder einen, der rau und schnell ist?
Da die STL nicht vorhersehen kann, welche die beste Wahl für Ihre Anwendung ist, muss die Standardeinstellung flexibler sein. Bäume "funktionieren einfach" und skalieren gut.
(C ++ 11 hat Hash-Tabellen mit hinzugefügt
unordered_map
. Sie können der Dokumentation entnehmen , dass Richtlinien festgelegt werden müssen, um viele dieser Optionen zu konfigurieren.)Was ist mit anderen Bäumen?
Rot-Schwarz-Bäume bieten eine schnelle Suche und sind im Gegensatz zu BSTs selbstausgleichend. Ein anderer Benutzer wies auf seine Vorteile gegenüber dem selbstausgleichenden AVL-Baum hin.
Alexander Stepanov (der Schöpfer von STL) sagte, dass er einen B * -Baum anstelle eines Rot-Schwarz-Baums verwenden würde, wenn er
std::map
erneut schreiben würde, da dies für moderne Speicher-Caches freundlicher ist.Sollten Karten immer Bäume verwenden?
Eine andere mögliche Kartenimplementierung wäre ein sortierter Vektor (Einfügungssortierung) und eine binäre Suche. Dies funktioniert gut für Container, die nicht häufig geändert, aber häufig abgefragt werden. Ich mache das oft in C as
qsort
und binbsearch
eingebaut.Muss ich überhaupt eine Karte verwenden?
Cache Überlegungen bedeuten , es selten sinnvoll Gebrauch macht
std::list
oderstd::deque
überstd:vector
selbst für jene Situationen , die wir in der Schule gelernt hatten (wie zum Beispiel ein Element aus der Mitte der Liste zu entfernen). Wenn Sie dieselbe Argumentation anwenden, ist die Verwendung einer for-Schleife für die lineare Suche in einer Liste häufig effizienter und sauberer als die Erstellung einer Karte für einige Suchvorgänge.Natürlich ist die Auswahl eines lesbaren Containers normalerweise wichtiger als die Leistung.
quelle
Update 14.06.2017: webbertiger bearbeitet seine Antwort, nachdem ich einen Kommentar abgegeben habe. Ich sollte darauf hinweisen, dass die Antwort für meine Augen jetzt viel besser ist. Aber ich habe meine Antwort nur als zusätzliche Information behalten ...
Aufgrund der Tatsache, dass ich denke, dass die erste Antwort falsch ist (Korrektur: nicht mehr beide) und die dritte eine falsche Bestätigung hat. Ich habe das Gefühl, ich musste Dinge klären ...
Die 2 beliebtesten Bäume sind AVL und Red Black (RB). Der Hauptunterschied liegt in der Nutzung:
Der Hauptunterschied liegt in der Färbung. Sie haben im RB-Baum weniger Neuausgleichsaktionen als in AVL, da Sie durch die Färbung manchmal Neuausgleichsaktionen überspringen oder verkürzen können, die relativ hohe Kosten verursachen. Aufgrund der Färbung hat der RB-Baum auch eine höhere Knotenebene, da er rote Knoten zwischen schwarzen Knoten aufnehmen kann (mit der Möglichkeit von ~ 2x mehr Ebenen), was das Suchen (Lesen) etwas weniger effizient macht ... aber weil es eine ist konstant (2x), bleibt es in O (log n).
Wenn Sie den Leistungstreffer für eine Änderung eines Baums (signifikant) im Vergleich zum Leistungstreffer bei der Konsultation eines Baums (fast unbedeutend) betrachten, ist es für einen allgemeinen Fall selbstverständlich, RB gegenüber AVL zu bevorzugen.
quelle
Es ist nur die Wahl Ihrer Implementierung - sie können als jeder ausgeglichene Baum implementiert werden. Die verschiedenen Auswahlmöglichkeiten sind alle mit geringfügigen Unterschieden vergleichbar. Deshalb ist jeder so gut wie jeder.
quelle