Umfassendes Verständnis klassischer rot-schwarzer Bäume | JD Logistics Technical Team

In diesem Artikel sprechen wir über die klassische Implementierung von Rot-Schwarz-Bäumen. Die Implementierung von Rot-Schwarz-Bäumen in Java verwendet klassische Rot-Schwarz-Bäume. Im vorherigen Artikel haben wir den linksgerichteten rot-schwarzen Baum vorgestellt. Er ist relativ einfach. Sie müssen den vorherigen Artikel lesen, bevor Sie diesen lesen, da Grundkenntnisse wie Rotation in diesem Artikel nicht wiederholt werden. Der größte Teil des Inhalts dieses Artikels bezieht sich auf „Einführung in Algorithmen“ und den Quellcode für die Implementierung von Rot-Schwarz-Bäumen in Java. Ich hoffe, Sie können ihn geduldig lesen.

Bevor wir mit dem Text beginnen, schauen wir uns die folgenden Fragen an:

  • Warum werden rot-schwarze Bäume häufiger verwendet als AVL-Bäume?

In Bezug auf Rot-Schwarz-Bäume und AVL-Bäume haben Sie möglicherweise gelesen: „Im schlimmsten Fall sind die Suchzeiten von AVL-Bäumen und Rot-Schwarz-Bäumen logarithmisch. Obwohl die Koeffizienten von Rot-Schwarz-Bäumen höher sind, gibt es keinen wesentlichen Unterschied.“ , ist tolerierbar. Das Schlimmste an AVL-Bäumen ist, dass die Anzahl der Drehungen beim Löschen von Knoten logarithmisch ist, während rot-schwarze Bäume nur bis zu 3 Drehungen erfordern, was dazu führt, dass rot-schwarze Bäume häufiger verwendet werden als AVL-Bäume. "Mehr" Sichtweise, aber tatsächlich ist dies nicht der Hauptgrund. Der Hauptgrund ist, dass die amortisierte Zeitkomplexität des Rot-Schwarz-Baums bei O(1) bleibt, wenn Einfüge- und Löschvorgänge in einer willkürlichen Reihenfolge gemischt werden. , während die amortisierte Zeitkomplexität des AVL-Baums Die Zeitkomplexität beträgt O(logn) .

Der klassische Rot-Schwarz-Baum ist isomorph zum 2-3-4-Suchbaum . Im Vergleich zur Implementierung des linksgerichteten Rot-Schwarz-Baums (2-3-Baum) ist der Aufwand für die Aufrechterhaltung des Gleichgewichts des Rot-Schwarz-Baums geringer. schwarzer Baum. Im Folgenden bezeichnen wir klassische Rot-Schwarz-Bäume kurz als Rot-Schwarz-Bäume. Fangen wir an: 

2-3-4 Suchbaum

Der 2-3-4-Suchbaum fügt dem 2-3-Suchbaum 4 Knoten hinzu. Die 4 Knoten wurden im vorherigen Artikel vorgestellt. Schauen wir uns zunächst an, wie der 2-3-4-Suchbaum aussieht:

2-3-4 Baum.jpg

Wir werden hier nicht näher darauf eingehen, wann ein neuer Knoten in einen 2-Knoten oder einen 3-Knoten eingefügt wird. Konzentrieren wir uns auf die Situation, wenn ein neuer Knoten in einen 4-Knoten eingefügt wird. Wenn ein neuer Knoten in einen 4-Knoten eingefügt wird, muss der 4-Knoten zuerst in drei 2-Knoten umgewandelt werden, und dann wird der Einfügevorgang in einem der 2-Knoten ausgeführt, wie unten gezeigt, wo der gelbe Knoten ist ist der neu eingefügte Knoten. Der Knoten entspricht den vier Situationen des Einfügens in den 4-Knoten:

2-3-4 Baum 2.jpg

Nehmen wir noch einmal Fall (4) als Beispiel und fügen den Knoten mit dem Wert 34 in den 2-3-4-Suchbaum ein:

2-3-4 Baum 3.jpg

Nachdem Sie den 4-Knoten in 3 2-Knoten umgewandelt und das Einfügen des neuen Knotens 34 abgeschlossen haben, müssen Sie den „Wurzelknoten 25“ in den übergeordneten Knoten einfügen, wie in der Abbildung oben gezeigt. Dies ähnelt im Wesentlichen dem, worüber wir im vorherigen Artikel im 2-3-Suchbaum gesprochen haben. Es ist notwendig, den temporären 5-Knoten kontinuierlich zu zerlegen und den ursprünglichen 4-Knoten in drei 2-Knoten-Wurzelknoten zu zerlegen und diese einzufügen in den höheren übergeordneten Knoten. Konvertieren Sie ihn im Knoten, bis er auf einen 2-Knoten oder einen 3-Knoten trifft, in einen Knoten, der nicht zerlegt werden muss. Wenn er schließlich in den Wurzelknoten eingefügt wird, um ihn zu einem 5-Knoten zu machen Knoten, es muss auch zerlegt und dann eingefügt werden. Nach Fertigstellung Die Höhe des Baumes plus eins .

Durch die Einfügungsoperation des 2-3-4-Baums werden die Änderungen am Baum selbst lokal vorgenommen. Zusätzlich zu den relevanten Knoten und Referenzen müssen keine anderen Teile des Baums geändert und überprüft werden. Diese lokalen Transformationen haben keine Auswirkungen auf die globale Ordnung und Ordnung des Baums. Balance: Während des Einfügevorgangs ist der 2-3-4-Baum immer ein perfekt ausgeglichener Binärbaum .

Klassischer rot-schwarzer Baum

Der klassische Rot-Schwarz-Baum ist isomorph zum 2-3-4-Suchbaum. Wenn wir das Beispiel des 2-3-4-Suchbaums in einen Rot-Schwarz-Baum umwandeln, sieht es wie in der Abbildung unten dargestellt aus (Links, die auf verweisen). rote Knoten sind auch rot gefärbt):

Klassischer rot-schwarzer Baum.jpg

Es erfüllt die folgenden Eigenschaften:

  • Die Knotenfarbe ist rot oder schwarz

  • Der Wurzelknoten ist schwarz

  • Blattknoten (Nullknoten) sind schwarz (Nullknoten werden im Bild nicht gezeichnet)

  • Die beiden untergeordneten Knoten eines roten Knotens sind schwarz (durchgehende rote Knoten können nicht angezeigt werden).

  • Die Anzahl der schwarzen Knoten auf dem Pfad von jedem Blattknoten zum Wurzelknoten ist gleich, das heißt, der Baum ist schwarz ausgeglichen.

Über die Eigenschaft des Schwarzgleichgewichts haben wir bereits bei der Erläuterung linksgerichteter Rot-Schwarz-Bäume gesprochen. Hier machen wir uns die Mühe, es noch einmal zu beschreiben: Der 2-3-4-Baum kann immer das perfekte Gleichgewicht, also den Abstand zu jedem Blatt, aufrechterhalten Knoten und Wurzelknoten sind gleich. Der rot-schwarze Baum ist auch ein 2-3-4-Suchbaum, und die darin enthaltenen schwarzen Links sind gewöhnliche Links im 2-3-4-Suchbaum, sodass die schwarzen Knoten referenziert werden Auch die schwarzen Verbindungen im rot-schwarzen Baum müssen perfekt ausbalanciert sein, sodass die Anzahl der schwarzen Knoten auf dem Pfad von jedem Blattknoten zum Wurzelknoten gleich sein muss.

Nachfolgend kombinieren wir die Diagramme und den Quellcode in Java, um das Einfügen und Löschen von Knoten im rot-schwarzen Baum zu erklären: TreeMap 

Knotendefinition

    static final class Entry<K,V> implements Map.Entry<K,V> {
        K key;
        V value;
        Entry<K,V> left;
        Entry<K,V> right;
        Entry<K,V> parent;
        boolean color = BLACK;

        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
        // ...
    }

Wir können feststellen, dass die Knotendefinition zusätzlich zu den Farbinformationen und Verweisen auf den linken und rechten untergeordneten Knoten auch einen Verweis auf den übergeordneten Knoten hinzufügt. parent 

Knoten einfügen

2-Knoten einfügen

Es ist sehr einfach, Knoten direkt in 2-Knoten einzufügen. In den beiden in der folgenden Abbildung gezeigten Fällen werden 2-Knoten in 3-Knoten umgewandelt:

Klassischer rot-schwarzer Baum 2.jpg

3-Knoten einfügen

Um einen 3-Knoten einzufügen, müssen wir ihn in zwei Fällen besprechen: linksschiefer 3-Knoten und rechtsschiefer 3-Knoten:

  1. Das Einfügen des linken oder rechten Knotens eines nach links geneigten 3-Knotens erfordert die Konvertierung in einen 4-Knoten, wie in der folgenden Abbildung dargestellt:

Klassischer rot-schwarzer Baum 3.jpg

  1. Um den linken oder rechten Knoten eines nach rechts geneigten 3-Knotens einzufügen, müssen Sie ihn in einen 4-Knoten umwandeln, wie in der folgenden Abbildung dargestellt:

Klassischer rot-schwarzer Baum 4.jpg

4-Knoten einfügen

In den beiden oben genannten Fällen findet keine „Verschmelzung nach oben“ statt. Wenn ein 4-Knoten eingefügt wird, muss er in 3 2-Knoten zerlegt werden, und dann wird der „Wurzelknoten“ des 2-Knotens mit seinem übergeordneten Knoten zusammengeführt Knoten (Wenn ja), müssen wir auch von Fall zu Fall diskutieren:

  1. Fügen Sie den roten Knoten links vom 4-Knoten ein:

Klassischer rot-schwarzer Baum 5.jpg

  1. Fügen Sie den roten Knoten rechts vom 4-Knoten ein:

Klassischer rot-schwarzer Baum 6.jpg

Derzeit wurden alle Situationen zum Einfügen neuer Knoten besprochen. Schauen wir uns den Quellcode an und achten Sie auf die Kommentare: TreeMap 

    public V put(K key, V value) {
        Entry<K,V> t = root;
        // 插入第一个节点
        if (t == null) {
            compare(key, key);

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // 根据比较器找到插入节点的位置
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        // 根据大小关系添加新的节点
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        // *插入之后修复平衡操作*
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

Methoden , auf die man sich konzentrieren muss : fixAfterInsertion 

    private void fixAfterInsertion(Entry<K,V> x) {
        // 新插入的节点指定为红色
        x.color = RED;

        // 如果非空非根节点且有连续的红色节点出现,需要不断地修复平衡
        while (x != null && x != root && x.parent.color == RED) {
            // 插入节点后,出现连续红色的节点的位置在左侧
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                // 插入的是4-节点,对应插入4-节点的情况1
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                if (colorOf(y) == RED) {
                    // 反色处理
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    // 处理父节点的父节点(因为该节点为红色,可能会发生向上合并的操作)
                    x = parentOf(parentOf(x));
                } else {
                    // 如下步骤对应插入3-节点的情况1
                    // 插入的是3-节点的右节点
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        // 左旋父节点
                        rotateLeft(x);
                    }
                    // 现在转换成了插入位置为3-节点的左节点,父节点染成黑色
                    setColor(parentOf(x), BLACK);
                    // 父节点的父节点为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 右旋父节点的父节点,转换成4-节点
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                // 插入节点后,出现连续红色的节点的位置在右侧
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                // 插入的是4-节点,对应插入4-节点的情况2
                if (colorOf(y) == RED) {
                    // 反色处理
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    // 处理父节点的父节点(因为该节点为红色,可能会发生向上合并的操作)
                    x = parentOf(parentOf(x));
                } else {
                    // 如下步骤对应插入3-节点的情况2
                    // 插入的是3-节点的左节点
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        // 右旋父节点
                        rotateRight(x);
                    }
                    // 转换成了插入位置为3-节点的右节点,父节点为黑色
                    setColor(parentOf(x), BLACK);
                    // 父节点的父节点为红色
                    setColor(parentOf(parentOf(x)), RED);
                    // 左旋父节点的父节点,转换成4-节点
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        // 根节点始终为黑色
        root.color = BLACK;
    }

*Knoten löschen

Das Löschen eines Knotens stellt die komplexeste Implementierung in einem rot-schwarzen Baum dar. Wenn Sie einen 3- oder 4-Knoten löschen (der rote Knoten wird gelöscht), hat das Entfernen des Knotens keinen Einfluss auf das Gleichgewicht des rot-schwarzen Baums ; Wenn Sie einen 2-Knoten-Knoten löschen, müssen Sie nach dem Löschen des Knotens das Gleichgewicht des rot-schwarzen Baums nach oben wiederherstellen. Dies ist auch der komplizierteste Teil. Lassen Sie uns ihn anhand der in „Einführung in Algorithmen“ besprochenen Situation analysieren ". Um den Löschvorgang zu verstehen, müssen Sie mit den Eigenschaften des Rot-Schwarz-Baums bestens vertraut sein. Das Buch ist vertraut mit (insbesondere Eigenschaft 5: Rot-Schwarz-Bäume sind immer schwarz-ausgeglichen) und bespricht vier Situationen nach dem Entfernen der 2 -Knoten. Beachten Sie, dass der Knoten, an dem die Neuausrichtung des Rot-Schwarz-Baums beginnt, x ist (der Knoten x ist nach dem Entfernen eines bestimmten 2-Knotens an dieser Position gespleißt), sein Geschwisterknoten ist w, wie folgt:

Fall 1: Der Geschwisterknoten w von x ist rot

LÖSCHEN.jpg

Der in der obigen Abbildung gezeigte lokale Rot-Schwarz-Baum wird schwarz ausgeglichen, bevor der Knoten entfernt wird. Dann sollte die Anzahl der schwarzen Knoten vom Wurzelknoten bis zum Unterbaum 3 betragen. Wenn der Knoten entfernt wird, beträgt die Anzahl der schwarzen Knoten vom Wurzelknoten zum a-Teilbaum 2 (wie in der Abbildung gezeigt); die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zum c-Teilbaum beträgt 2.

Nach der Inversion und der Linksrotationsverarbeitung wird der ursprüngliche Wurzelknoten rot gefärbt. Jetzt beträgt die Anzahl der schwarzen Knoten vom „neuen Wurzelknoten“ zum a-Teilbaum immer noch 2 und wurde nicht zu 3 hinzugefügt, so dass die schwarzen Knoten vorhanden sind immer noch unausgeglichen; zum c-Teilbaum Die Anzahl der schwarzen Knoten auf dem Pfad beträgt 2, was derselbe ist wie vor der Operation und keinen Einfluss auf die Eigenschaften des rot-schwarzen Baums hat.

Nach der Farbinvertierung und Linksdrehung wird Fall 1 zu Fall 2, Fall 3 oder Fall 4 und weiterverarbeitet. Das ist eigentlich nicht schwer zu verstehen. Kombiniert mit der Eigenschaft 5 des Rot-Schwarz-Baums wird die Invertierung und Durch die Linksdrehung wird der Wurzelknoten zu einem bestimmten Teilbaumpfad verschoben. Die Anzahl der schwarzen Knoten hat sich nicht geändert, und ein schwarzer Knoten wurde aus dem Pfad entfernt, als wir den Löschvorgang durchführten, also vom Pfad zum „Wurzelknoten“. " Zu einem bestimmten Teilbaum muss noch ein schwarzer Knoten hinzugefügt werden, um den Schwarzabgleich zu erfüllen. Der Schlüssel Die Idee ist, dass die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zu jedem Teilbaum nicht geändert werden kann und Eigenschaft 5 wahr sein muss vor und nach der Knotenlöschung und -transformation .

Fall 2: Der Geschwisterknoten w von x ist schwarz und die beiden untergeordneten Knoten von w sind beide schwarz.

DELETE2.jpg

Beachten Sie, dass die roten und schwarzen Farbverläufe in der Abbildung darauf hinweisen, dass der Knoten ein schwarzer oder ein roter Knoten sein kann. In diesem Fall sind beide untergeordneten Knoten des Geschwisterknotens schwarz und die Anzahl der Wurzelknoten auf dem Pfad vom Wurzelknoten zum a-Teilbaum und zum c-Teilbaum, bevor ein Knoten gelöscht wird, beträgt jeweils 2 (Wurzelknoten mit unsicherer Farbe werden ignoriert). . Nach dem Löschen des Knotens beträgt die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zum a-Teilbaum 1. Wenn wir den w-Knoten rot färben, beträgt die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zum c-Teilbaum Auf 1 reduziert, dann vom Wurzelknoten zum Bild. Die Anzahl der schwarzen Knoten auf allen Teilbaumpfaden im Diagramm ist gleich, was bedeutet, dass der lokale rot-schwarze Baum im Diagramm die Bedingungen des Schwarzabgleichs erfüllt. Zu diesem Zeitpunkt müssen Sie x auf seinen übergeordneten Knoten verweisen und den Reparaturvorgang (während) wiederholen.

Fall 3: Der Geschwisterknoten w von x ist schwarz, der linke Knoten von w ist rot und der rechte Knoten von w ist schwarz.

DELETE3.jpg

In diesem Fall beträgt die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zum Teilbaum e 2. Nach dem Austausch der Farben von w und w.left und rechtsdrehenden Operationen hat dies keinen Einfluss auf die Eigenschaften des rot-schwarzen Baums . Dadurch wird Situation 3 in Szenario 4 umgewandelt.

Fall 4: Der Geschwisterknoten w von x ist schwarz und der rechte Knoten von w ist rot

DELETE4.jpg

In Fall 4 beträgt die Anzahl der schwarzen Knoten auf dem Pfad vom Wurzelknoten zum Unterbaum 1 und muss auf 2 erhöht werden, um einen Schwarzausgleich zu erreichen. Durch Austauschen der Farben des w-Knotens und des x.parent-Knotens, anschließendes Färben des w.right-Knotens in Schwarz und anschließendes Durchführen einer Linksdrehung am x.parent-Knoten wird die Anzahl der schwarzen Knoten auf dem Pfad vom Der Wurzelknoten zum a-Teilbaum erhöht sich auf 2, und die Anzahl der schwarzen Knoten auf dem Pfad zu anderen c-, d-, e- und f-Teilbäumen bleibt unverändert und zerstört nicht die Eigenschaften des rot-schwarzen Baums. Nachdem der Vorgang abgeschlossen ist, Zeigen Sie x auf den Wurzelknoten und beenden Sie die Schleife, da der rot-schwarze Baum zu diesem Zeitpunkt alle Eigenschaften erfüllt.

Nachdem wir nun die vier verschiedenen Situationen nach dem Löschen des 2-Knotens analysiert haben, müssen wir alle den Rot-Schwarz-Baum durch Färbe- und Rotationsoperationen wieder schwarz ausbalancieren. Schauen wir uns die Implementierung der folgenden Methode an, und wir müssen es tun Beachten Sie die Anmerkungsinformationen: TreeMap  remove 

    /**
     * 删除节点
     */
    public V remove(Object key) {
        // 找到要删除的节点
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        // 执行删除操作
        deleteEntry(p);
        return oldValue;
    }

Schauen wir uns als Nächstes die Ausführungslogik des Löschvorgangs an:

    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;

        // 如果该节点有左右子节点,则它是一个要被删除的内部节点
        // 那么需要找到该节点的“后继节点”,找到之后删除任务则变为了删除该后继节点
        if (p.left != null && p.right != null) {
            Entry<K,V> s = successor(p);
            p.key = s.key;
            p.value = s.value;
            p = s;
        }

        // 执行完上述代码块后,被删除节点要么为叶子节点,要么为只有左子树或右子树的节点
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);

        // 被删除节点有左子树或右子树
        if (replacement != null) {
            // 修正节点的父节点引用关系
            replacement.parent = p.parent;
            // 被删除节点为根节点的关系
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                // 被删除的节点是左节点
                p.parent.left  = replacement;
            else
                // 被删除的节点是右节点
                p.parent.right = replacement;

            // 断开被删除节点的所有引用关系
            p.left = p.right = p.parent = null;

            // 如果被删除节点为2-节点,需要执行再平衡操作
            if (p.color == BLACK)
                fixAfterDeletion(replacement);
        } else if (p.parent == null) {
            // 红黑树只有一个节点的情况
            root = null;
        } else {
            // 删除叶子节点,如果该节点为2-节点那么需要再平衡修复
            if (p.color == BLACK)
                fixAfterDeletion(p);

            // 断开引用关系
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    // 获取 t 节点的后继节点
    static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
        if (t == null)
            return null;
        else if (t.right != null) {
            // 右子树不为空,则右子树中最小的节点为该节点的后继节点
            Entry<K,V> p = t.right;
            while (p.left != null)
                p = p.left;
            return p;
        } else {
            // ...
        }
    }

fixAfterDeletion Um die Methode nach dem Löschen wieder ins Gleichgewicht zu bringen, muss jeder auf die Anmerkungsinformationen achten:

    private void fixAfterDeletion(Entry<K,V> x) {
        // 非根节点和当前节点是黑色的才需要修复平衡
        while (x != root && colorOf(x) == BLACK) {
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));

                // 情况 1
                if (colorOf(sib) == RED) {
                    // 反色处理:兄弟节点染黑,父节点染红
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    // 左旋父节点
                    rotateLeft(parentOf(x));
                    // 新的兄弟节点
                    sib = rightOf(parentOf(x));
                }

                // 情况 2
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    // 将兄弟节点染红后,x指向父节点引用,继续修复平衡
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    // 情况 3
                    if (colorOf(rightOf(sib)) == BLACK) {
                        // 交换颜色:兄弟节点左节点染黑,兄弟节点染红
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        // 右旋兄弟节点
                        rotateRight(sib);
                        // 新的兄弟节点
                        sib = rightOf(parentOf(x));
                    }
                    // 情况 4
                    // 交换兄弟节点和父节点的颜色
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    // 将兄弟节点的右节点由红染黑
                    setColor(rightOf(sib), BLACK);
                    // 左旋父节点
                    rotateLeft(parentOf(x));
                    // 满足红黑树的性质,指向根节点循环结束
                    x = root;
                }
            } else {
                // 以下是镜像操作
                Entry<K,V> sib = leftOf(parentOf(x));

                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }

                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }

        // 情况 2,x 为红色节点时跳出循环,需要将 x 节点染黑
        // 因为红黑树中不存在连续的红色节点
        setColor(x, BLACK);
    }

Wie hoch ist die zeitliche Komplexität der Ausführung der Löschmethode für einen rot-schwarzen Baum? Die Höhe eines rot-schwarzen Baums mit n Knoten ist logn. fixAfterDeletion Wenn keine Methode aufgerufen wird, beträgt die Komplexität O(logn). In den Fällen 1, 3 und 4 führt jeder eine konstante Anzahl von Farbwechseln und höchstens 3 Rotationen durch . Beenden, nur in Fall 2 ist es möglich, das Gleichgewicht wiederholt zu reparieren, der Zeiger steigt auch höchstens O(logn)-mal an und es gibt keine Rotationsoperation, sodass die Komplexität O(logn) beträgt, mit maximal 3 Umdrehungen , also die Rot-Schwarz-Baum-Löschmethode Die zeitliche Komplexität beträgt O(logn). fixAfterDeletion  fixAfterDeletion 


die Schultern des Riesen

  • „Einführung in Algorithmen“: Kapitel 13 Rot-Schwarze Bäume

  • Zhihu – Einige Gedanken zu AVL-Bäumen und rot-schwarzen Bäumen

  • LeetCode – Rot-schwarzer Baum vom Eintrag bis zur Einführung

  • Blog Garden – Löschung roter und schwarzer Bäume

  • Autor: JD Logistics Wang Yilong

    Quelle: JD Cloud Developer Community Ziyuanqishuo Tech Bitte geben Sie beim Nachdruck die Quelle an

Bilibili stürzte zweimal ab, Tencents „3.29“-Unfall erster Stufe … Bestandsaufnahme der zehn häufigsten Ausfallunfälle im Jahr 2023 Vue 3.4 „Slam Dunk“ veröffentlichte MySQL 5.7, Moqu, Li Tiaotiao … Bestandsaufnahme des „Stopps“ im Jahr 2023 Mehr ” (Open-Source-)Projekte und Websites blicken auf die IDE von vor 30 Jahren zurück: nur TUI, helle Hintergrundfarbe... Vim 9.1 wird veröffentlicht, gewidmet Bram Moolenaar, dem Vater von Redis, „Rapid Review“ LLM Programming: Omniscient und Omnipotent&& Stupid „Post-Open Source“ Die Ära ist gekommen: Die Lizenz ist abgelaufen und kann nicht mehr für die breite Öffentlichkeit bereitgestellt werden. China Unicom Broadband begrenzte plötzlich die Upload-Geschwindigkeit und eine große Anzahl von Benutzern beschwerte sich. Windows-Führungskräfte versprachen Verbesserungen: Machen Sie den Anfang Speisekarte wieder super. Niklaus Wirth, der Vater von Pascal, ist verstorben.
{{o.name}}
{{m.name}}

Supongo que te gusta

Origin my.oschina.net/u/4090830/blog/10584600
Recomendado
Clasificación