Überall, wo ich hinschaue, werden Datenstrukturen mithilfe von rot-schwarzen Bäumen implementiert ( std::set
in C ++, SortedDictionary
in C # usw.).
Nachdem ich gerade (a, b), Rot-Schwarz- und AVL-Bäume in meinem Algorithmus-Kurs behandelt habe, habe ich Folgendes herausgefunden (auch nachdem ich mich bei Professoren erkundigt, ein paar Bücher durchgesehen und ein bisschen gegoogelt habe):
- AVL-Bäume haben eine geringere durchschnittliche Tiefe als rot-schwarze Bäume, und daher ist die Suche nach einem Wert im AVL-Baum durchgehend schneller.
- Rot-schwarze Bäume führen weniger strukturelle Änderungen durch, um sich auszugleichen als AVL-Bäume, wodurch sie möglicherweise schneller zum Einfügen / Löschen verwendet werden können. Ich sage möglicherweise, weil dies von den Kosten der strukturellen Änderung des Baums abhängen würde, da dies sehr stark von der Laufzeit und der Implementierung abhängen wird (könnte in einer funktionalen Sprache auch völlig anders sein, wenn der Baum unveränderlich ist?)
Es gibt viele Online-Benchmarks, die AVL- und Rot-Schwarz-Bäume vergleichen. Was mich jedoch beeindruckt hat, ist, dass mein Professor im Grunde gesagt hat, dass Sie normalerweise eines von zwei Dingen tun würden:
- Entweder interessiert Sie die Leistung nicht so sehr. In diesem Fall spielt der Unterschied zwischen AVL und Rot-Schwarz in den meisten Fällen keine Rolle.
- Oder Sie legen großen Wert auf Leistung. In diesem Fall würden Sie sowohl AVL- als auch Rot-Schwarz-Bäume wegwerfen und sich für B-Bäume entscheiden, die optimiert werden können, um besser zu funktionieren (oder (a, b) -Bäume, I '). Ich werde alle in einen Korb legen.)
Der Grund dafür ist, dass ein B-Baum Daten kompakter im Speicher speichert (ein Knoten enthält viele Werte), und es wird viel weniger Cache-Fehlschläge geben. Sie können die Implementierung auch basierend auf dem Anwendungsfall optimieren und die Reihenfolge des B-Baums von der CPU-Cache-Größe usw. abhängig machen.
Das Problem ist, dass ich kaum eine Quelle finden kann, die die tatsächliche Verwendung verschiedener Implementierungen von Suchbäumen auf moderner Hardware analysiert. Ich habe viele Bücher über Algorithmen durchgesehen und nichts gefunden, das verschiedene Baumvarianten miteinander vergleichen könnte, außer zu zeigen, dass einer eine geringere durchschnittliche Tiefe hat als der andere (was nicht wirklich aussagt, wie sich der Baum verhält) in echten Programmen.)
Abgesehen davon, gibt es einen besonderen Grund, warum überall rot-schwarze Bäume verwendet werden, wenn auf der Grundlage der obigen Aussagen B-Bäume diese übertreffen sollten? (Als einziger Benchmark konnte ich auch http://lh3lh3.users.sourceforge.net/udb.shtml finden , aber es könnte nur eine Frage der spezifischen Implementierung sein). Oder ist es der Grund, warum jeder rot-schwarze Bäume verwendet, weil sie ziemlich einfach zu implementieren oder anders ausgedrückt, schwer zu implementieren sind?
Wie ändert sich dies auch, wenn man in den Bereich der funktionalen Sprachen wechselt? Es scheint, dass sowohl Clojure als auch Scala Hash-Array-zugeordnete Versuche verwenden , wobei Clojure einen Verzweigungsfaktor von 32 verwendet.
Antworten:
Zitat aus der Antwort auf die Frage „ Traversale von der Wurzel in AVL-Bäumen und rot-schwarzen Bäumen “
Damit ein RedBlack-Tree-Insert ohne Rekursion implementiert werden kann, ist die Rekursion auf einigen CPUs sehr teuer, wenn Sie den Funktionsaufruf-Cache überlaufen (z. B. SPARC aufgrund der Verwendung des Register-Fensters ).
(Ich habe gesehen, dass Software auf dem Sparc durch Entfernen eines Funktionsaufrufs über 10-mal so schnell ausgeführt wurde, was dazu führte, dass der häufig aufgerufene Codepfad für das Registerfenster zu tief war. Da Sie nicht wissen, wie tief das Registerfenster eingeschaltet sein wird Das System Ihres Kunden und Sie wissen nicht, wie weit Sie sich in der Anrufliste im "Hot-Code-Pfad" befinden, ohne eine Rekursion zu verwenden.
Es ist auch ein Vorteil, nicht zu riskieren, dass der Stapel ausgeht.
quelle
Ich habe in letzter Zeit auch dieses Thema recherchiert. Hier sind meine Ergebnisse, aber bedenke, dass ich kein Experte für Datenstrukturen bin!
Es gibt einige Fälle, in denen Sie B-Bäume überhaupt nicht verwenden können.
Ein prominenter Fall stammt
std::map
aus C ++ STL. Der Standard verlangt, dassinsert
vorhandene Iteratoren nicht ungültig werdenhttp://en.cppreference.com/w/cpp/container/map/insert
Dies schließt B-Tree als Implementierung aus, da sich das Einfügen um vorhandene Elemente bewegen würde.
Ein weiterer ähnlicher Anwendungsfall sind aufdringliche Datenstrukturen. Das heißt, anstatt Ihre Daten im Knoten des Baums zu speichern, speichern Sie Zeiger auf Kinder / Eltern in Ihrer Struktur:
Sie können einen B-Baum einfach nicht aufdringlich machen, da es sich nicht um eine reine Zeigerdatenstruktur handelt.
Aufdringliche rot-schwarze Bäume werden beispielsweise in jemalloc verwendet , um freie Speicherblöcke zu verwalten. Dies ist auch eine beliebte Datenstruktur im Linux-Kernel.
Ich glaube auch, dass die "Single Pass Tail Recursive" -Implementierung nicht der Grund für die Beliebtheit von Rot-Schwarz-Bäumen als veränderbare Datenstruktur ist.
Die in opendatastructures beschriebene Variante verwendet übergeordnete Zeiger, einen rekursiven Down-Pass zum Einfügen und einen iterativen Loop-Up-Pass für Korrekturen. Die rekursiven Aufrufe befinden sich in einer Endposition und Compiler optimieren dies zu einer Schleife (ich habe dies in Rust überprüft).
quelle
Nun, dies ist keine maßgebliche Antwort, aber wenn ich einen ausgeglichenen binären Suchbaum codieren muss, ist es ein rot-schwarzer Baum. Dafür gibt es einige Gründe:
1) Die durchschnittlichen Einfügungskosten sind für rot-schwarze Bäume konstant (wenn Sie nicht suchen müssen), während sie für AVL-Bäume logarithmisch sind. Darüber hinaus handelt es sich höchstens um eine komplizierte Umstrukturierung. Im schlimmsten Fall ist es immer noch O (log N), aber das sind nur einfache Umfärbungen.
2) Sie benötigen nur 1 Bit an zusätzlichen Informationen pro Knoten, und Sie können häufig einen Weg finden, um diese kostenlos zu erhalten.
3) Ich muss das nicht sehr oft machen, also muss ich jedes Mal, wenn ich es mache, herausfinden, wie ich es noch einmal machen soll. Die einfachen Regeln und die Korrespondenz mit 2-4 Bäumen lassen es jedes Mal einfach erscheinen , auch wenn sich der Code jedes Mal als kompliziert herausstellt . Ich hoffe immer noch, dass der Code eines Tages einfach wird.
4) Die Art und Weise, wie der rot-schwarze Baum den entsprechenden 2-4-Baumknoten aufteilt und den mittleren Schlüssel durch einfaches Umfärben in den übergeordneten 2-4-Knoten einfügt, ist sehr elegant. Ich liebe es einfach, es zu tun.
quelle
Rot-Schwarz- oder AVL-Bäume haben einen Vorteil gegenüber B-Bäumen und dergleichen, wenn der Schlüssel lang ist oder aus einem anderen Grund das Verschieben eines Schlüssels teuer ist.
Ich habe
std::set
aus verschiedenen Gründen meine eigene Alternative zu einem Großprojekt geschaffen. Ich habe aus Leistungsgründen AVL gegenüber Rot-Schwarz gewählt (aber diese kleine Leistungsverbesserung war nicht die Rechtfertigung, meine eigene anstelle von std :: set zu rollen). Der "Schlüssel", der kompliziert und schwer zu bewegen war, war ein bedeutender Faktor. Sind (a, b) Bäume immer noch sinnvoll, wenn Sie eine andere Indirektionsebene vor den Schlüsseln benötigen? AVL- und rot-schwarze Bäume können ohne Verschieben von Schlüsseln umstrukturiert werden, sodass sie den Vorteil haben, wenn das Verschieben von Schlüsseln teuer ist.quelle