【數(shù)據(jù)結(jié)構(gòu)】紅黑樹(shù)與2-3樹(shù)

什么是紅黑樹(shù)?

紅黑樹(shù)的定義

  • 每個(gè)節(jié)點(diǎn)或者是紅色的,或者是黑色的。
  • 根節(jié)點(diǎn)是黑色的。
  • 每一個(gè)葉子節(jié)點(diǎn)(最后的空節(jié)點(diǎn))是黑色的。
  • 如果一個(gè)節(jié)點(diǎn)是紅色的,那么他的孩子節(jié)點(diǎn)都是黑色的。
  • 從任意一個(gè)節(jié)點(diǎn)到葉子節(jié)點(diǎn),經(jīng)過(guò)的黑色節(jié)點(diǎn)是一樣的。

直接看到這些定義是非常難以理解的,紅黑樹(shù)為什么這樣定義?

在算法4這本書(shū)中對(duì)于紅黑樹(shù)的介紹直接繞過(guò)了紅黑樹(shù)的基本性質(zhì),而是首先探索了另外一種平衡樹(shù),這種平衡樹(shù)就是2-3樹(shù),事實(shí)上紅黑樹(shù)與2-3樹(shù)是等價(jià)的,如果理解的紅黑樹(shù)與2-3樹(shù)之間的等價(jià)關(guān)系,其實(shí)紅黑樹(shù)并不難!

什么是2-3樹(shù)?

在講解紅黑樹(shù)之前,首先學(xué)習(xí)2-3樹(shù),通過(guò)2-3樹(shù)來(lái)理解紅黑樹(shù)。

2-3樹(shù)滿足二分搜索樹(shù)的基本性質(zhì),但其節(jié)點(diǎn)可以存放一個(gè)元素或二個(gè)元素,每個(gè)節(jié)點(diǎn)有兩個(gè)或者三個(gè)孩子,所以稱(chēng)為2-3樹(shù)。通常存放一個(gè)元素有兩個(gè)孩子的節(jié)點(diǎn)稱(chēng)為兩節(jié)點(diǎn),一個(gè)元素有三個(gè)孩子的節(jié)點(diǎn)稱(chēng)為三節(jié)點(diǎn)。

如圖:

2-3樹(shù).png

==注意:2-3樹(shù)是一顆絕對(duì)平衡的樹(shù)。==

查找元素

2-3樹(shù)的查找類(lèi)似二分搜索樹(shù)的查找,根據(jù)元素的大小來(lái)決定查找的方向是左孩子還是右孩子。要判斷一個(gè)元素是否存在,首先將待查詢的元素與根節(jié)點(diǎn)進(jìn)行比較,如果待查找的元素和其中任意一個(gè)元素相等,那就查找到了,否則根據(jù)比較的結(jié)果來(lái)選擇查找的方向。

如圖:

2-3樹(shù)—查找元素.png

插入元素

==對(duì)于2-3樹(shù)來(lái)說(shuō),添加節(jié)點(diǎn)永遠(yuǎn)不會(huì)添加到空的位置上==。那么添加到哪呢?

如圖:

2-3樹(shù)—插入元素1.png

例如添加一個(gè)節(jié)點(diǎn)37,依然按照二分搜索樹(shù)的性質(zhì)來(lái)看待,由于37小于42,應(yīng)該放于42的左子樹(shù)中,由于42的左子樹(shù)為空,那么此時(shí)新節(jié)點(diǎn)將會(huì)融合到添加的過(guò)程中所找到的最后一個(gè)葉子節(jié)點(diǎn)。

如圖:

2-3樹(shù)—插入元素2.png

由于42本來(lái)是個(gè)2節(jié)點(diǎn),但是融合后,變成了一個(gè)3節(jié)點(diǎn),并且依然是平衡的。

此時(shí)再添加一個(gè)節(jié)點(diǎn)12,由于12小于37,應(yīng)該放于37的左子樹(shù)中,由于37的左子樹(shù)為空,那么此時(shí)新節(jié)點(diǎn)將會(huì)融合到添加過(guò)程中所找到的最后一個(gè)葉子節(jié)點(diǎn)。

如圖:

2-3樹(shù)—插入元素3.png

雖然再添加過(guò)程中的最后一個(gè)葉子節(jié)點(diǎn)是一個(gè)3節(jié)點(diǎn),但是依然進(jìn)行融合,暫時(shí)形成一個(gè)4節(jié)點(diǎn)。但是對(duì)于2-3樹(shù)來(lái)說(shuō),它并沒(méi)有4節(jié)點(diǎn),只有2節(jié)點(diǎn)或3節(jié)點(diǎn)。對(duì)于4節(jié)點(diǎn)可以直接分裂成子樹(shù)。

如圖:

2-3樹(shù)—插入元素4.png

上圖中,由4節(jié)點(diǎn)分裂成了由三個(gè)2節(jié)點(diǎn)組成一顆平衡的樹(shù)。

此時(shí)再添加一個(gè)節(jié)點(diǎn)18,由于18小于37,進(jìn)入37的左子樹(shù)中繼續(xù)比較,此時(shí)18大于12,應(yīng)該放于12的右子樹(shù)中,由于12的右子樹(shù)為空,那么此時(shí)新節(jié)點(diǎn)將會(huì)融合到添加過(guò)程中所找到的最后一個(gè)葉子節(jié)點(diǎn)。

如圖:

2-3樹(shù)—插入元素5.png

此時(shí)再添加一個(gè)節(jié)點(diǎn)6,由于6小于37,進(jìn)入37的左子樹(shù)中繼續(xù)比較,此時(shí)6小于12,應(yīng)該放于12的左子樹(shù)當(dāng)中,但是12的左子樹(shù)為空,那么此時(shí)新節(jié)點(diǎn)將會(huì)融合到添加過(guò)程中所找到的最后一個(gè)葉子節(jié)點(diǎn)。

如圖:

2-3樹(shù)—插入元素6.png

由于2-3中沒(méi)有4節(jié)點(diǎn),對(duì)于節(jié)點(diǎn)可以直接分裂成子樹(shù)。

如圖:

2-3樹(shù)—插入元素7.png

注意,上圖中由于4節(jié)點(diǎn)分裂成3個(gè)2節(jié)點(diǎn)的子樹(shù),造成整個(gè)樹(shù)并不是一顆絕對(duì)平衡的樹(shù)。對(duì)于2-3樹(shù)來(lái)說(shuō),如果一個(gè)葉子節(jié)點(diǎn)本來(lái)是一個(gè)3節(jié)點(diǎn)了,添加了新節(jié)點(diǎn)變成了4節(jié)點(diǎn)的話,對(duì)于這個(gè)4節(jié)點(diǎn),分裂為三個(gè)2節(jié)點(diǎn),并且有一個(gè)根節(jié)點(diǎn),對(duì)于根節(jié)點(diǎn)需要跟父親節(jié)點(diǎn)進(jìn)行融合,對(duì)于根節(jié)點(diǎn)和父親節(jié)點(diǎn)融合分為兩種情況:

  • 父節(jié)點(diǎn)是一個(gè)2節(jié)點(diǎn)

    如圖:

    2-3樹(shù)—插入元素8.png
  • 父節(jié)點(diǎn)是一個(gè)3節(jié)點(diǎn)

    如圖:

    2-3樹(shù)—插入元素9.png
    2-3樹(shù)—插入元素10.png
    2-3樹(shù)—插入元素11.png

紅黑樹(shù)與2-3樹(shù)的等價(jià)性

2-3樹(shù)中的3節(jié)點(diǎn)的元素是并列關(guān)系,但兩個(gè)黑色的節(jié)點(diǎn)無(wú)法表示出并列的關(guān)系。那么如何在紅黑樹(shù)中標(biāo)識(shí)出并列的關(guān)系呢?

如圖:

紅黑樹(shù)與2-3樹(shù)的等價(jià)性1.png

如上圖中,可以將b節(jié)點(diǎn)設(shè)置為紅色,表示b、c節(jié)點(diǎn)在2-3樹(shù)中是并且的關(guān)系。也就是說(shuō),一個(gè)紅色節(jié)點(diǎn)和它的父親節(jié)點(diǎn)在2-3樹(shù)中是一個(gè)3節(jié)點(diǎn)。

紅黑樹(shù)的實(shí)現(xiàn)

注意:在這里實(shí)現(xiàn)的紅黑樹(shù)以紅色節(jié)點(diǎn)向左傾斜為基礎(chǔ)的。

插入節(jié)點(diǎn)

在2-3樹(shù)中插入一個(gè)節(jié)點(diǎn),始終都是與葉子節(jié)點(diǎn)進(jìn)行融合,那么在紅黑樹(shù)中紅色節(jié)點(diǎn)代表著與父親節(jié)點(diǎn)

并列的關(guān)系,所以在紅黑樹(shù)中添加一個(gè)節(jié)點(diǎn)始終都是紅色的。

紅黑樹(shù)—插入節(jié)點(diǎn)1.png

上圖中,添加第一個(gè)節(jié)點(diǎn)42,由于添加一個(gè)節(jié)點(diǎn)始終是紅色的,所以要把根節(jié)點(diǎn)變?yōu)楹谏?/p>

此時(shí),再添加一個(gè)節(jié)點(diǎn)37,根據(jù)二分搜索樹(shù)的性質(zhì),由于37<42,所以將節(jié)點(diǎn)37添加到根節(jié)點(diǎn)的左孩子。

紅黑樹(shù)—插入節(jié)點(diǎn)2.png
  • 情況1

    注意,如果新節(jié)點(diǎn)大于根節(jié)點(diǎn),就會(huì)將新節(jié)點(diǎn)插入到根節(jié)點(diǎn)的右孩子位置,那么此時(shí)就不符合紅色節(jié)點(diǎn)向左傾斜的定義了,如何解決?對(duì)根節(jié)點(diǎn)進(jìn)行左旋轉(zhuǎn)操作,并且根據(jù)紅色節(jié)點(diǎn)向左傾斜的定義,還需把37節(jié)點(diǎn)置位紅色,42節(jié)點(diǎn)置位黑色。注:這里對(duì)應(yīng)在2-3樹(shù)中相當(dāng)于把一個(gè)新的元素放于2節(jié)點(diǎn)中融合為3節(jié)點(diǎn)的操作。

    紅黑樹(shù)—插入節(jié)點(diǎn)3.png
  • 情況2

    此時(shí)再插入一個(gè)節(jié)點(diǎn)66,根據(jù)二分搜索樹(shù)的性質(zhì),由于66大于42,放于42的右孩子中。

    紅黑樹(shù)—插入節(jié)點(diǎn)4.png

    上圖中,在2-3樹(shù)中對(duì)應(yīng)一個(gè)臨時(shí)的4節(jié)點(diǎn),在2-3樹(shù)對(duì)臨時(shí)的4節(jié)點(diǎn)所處理的方式是什么?

    處理的方式是拆分成三個(gè)2節(jié)點(diǎn)所組成的一棵子樹(shù)。

    那么在紅黑樹(shù)中三個(gè)2節(jié)點(diǎn)代表著什么意思?其實(shí)就是代表這三個(gè)節(jié)點(diǎn)都是黑色的節(jié)點(diǎn),每一個(gè)黑色的節(jié)點(diǎn),如果它的左側(cè)沒(méi)有紅色節(jié)點(diǎn),它本身就代表一個(gè)單獨(dú)的二節(jié)點(diǎn),所以在這種情況下,節(jié)點(diǎn)根本不需要選擇操作,只需要改變顏色即可,讓節(jié)點(diǎn)42的左右孩子分別改為黑色。

    紅黑樹(shù)—插入節(jié)點(diǎn)5.png

    注:這個(gè)樣子相當(dāng)于2-3樹(shù)中由臨時(shí)4節(jié)點(diǎn)所組成三個(gè)2節(jié)點(diǎn)的子樹(shù)。

    注意,在2-3樹(shù)中由臨時(shí)4節(jié)點(diǎn)所組成的三個(gè)2節(jié)點(diǎn)的子樹(shù),有可能會(huì)造成整顆樹(shù)不是絕對(duì)平衡的,所以拆分的子樹(shù)的根節(jié)點(diǎn)會(huì)繼續(xù)向父親節(jié)點(diǎn)融合。這意味著,新的根節(jié)點(diǎn)在紅黑樹(shù)中變?yōu)榧t色節(jié)點(diǎn)。

    紅黑樹(shù)—插入節(jié)點(diǎn)6.png

    注:原來(lái)的42節(jié)點(diǎn)為黑色,37節(jié)點(diǎn)和66節(jié)點(diǎn)為紅色,經(jīng)過(guò)一系列操作之后,其實(shí)所有節(jié)點(diǎn)的顏色正好相反過(guò)來(lái)了,這個(gè)過(guò)程稱(chēng)為顏色翻轉(zhuǎn)。

  • 情況3

    紅黑樹(shù)—插入節(jié)點(diǎn)7.png

    如上圖,添加節(jié)點(diǎn)12,根據(jù)二分搜索樹(shù)的性質(zhì),添加到節(jié)點(diǎn)37的左孩子中。這樣的形狀也等同于2-3樹(shù)中的臨時(shí)4節(jié)點(diǎn)。

    紅黑樹(shù)—插入節(jié)點(diǎn)8.png

    在2-3樹(shù)中需要把臨時(shí)4節(jié)點(diǎn)拆分成三個(gè)2節(jié)點(diǎn),那么在紅黑樹(shù)中如何解決呢?

    對(duì)這顆子樹(shù)的根節(jié)點(diǎn)進(jìn)行右旋轉(zhuǎn)操作,旋轉(zhuǎn)后再對(duì)新的根節(jié)點(diǎn)變?yōu)樵瓉?lái)根節(jié)點(diǎn)的顏色。

    紅黑樹(shù)—插入節(jié)點(diǎn)9.png

    通過(guò)這樣的處理之后,其實(shí)本質(zhì)上12、37、42這些節(jié)點(diǎn)還是應(yīng)該是一個(gè)臨時(shí)的4節(jié)點(diǎn),所以最后再把原來(lái)的根節(jié)點(diǎn)變?yōu)榧t色節(jié)點(diǎn),這樣就表示節(jié)點(diǎn)42與父親節(jié)點(diǎn)37是融合在一起的。

    紅黑樹(shù)—插入節(jié)點(diǎn)10.png

    這樣就完成了整個(gè)右旋轉(zhuǎn)的過(guò)程。在這個(gè)過(guò)程中,根節(jié)點(diǎn)變?yōu)楣?jié)點(diǎn)37,并且臨時(shí)4節(jié)點(diǎn)的樣子變?yōu)楦?jié)點(diǎn)的左右孩子都為紅色節(jié)點(diǎn),這種情況就是顏色翻轉(zhuǎn),處理這種情況,把它變成對(duì)應(yīng)2-3樹(shù)中由三個(gè)2節(jié)點(diǎn)組成的子樹(shù)的方法,就是再運(yùn)行一下顏色翻轉(zhuǎn)的過(guò)程。

整體代碼

/**
 * 描述:紅黑樹(shù)。
 * <p>
 * Create By ZhangBiao
 * 2020/5/18
 */
public class RedBlackTree<K extends Comparable<K>, V> {

    private Node root;

    private int size;

    private static final boolean RED = true;

    private static final boolean BLACK = false;

    public RedBlackTree() {
        this.root = null;
        this.size = 0;
    }

    /**
     * 判斷節(jié)點(diǎn)node的顏色
     *
     * @param node
     * @return
     */
    private boolean isRed(Node node) {
        if (node == null) {
            return BLACK;
        }
        return node.color;
    }

    //   node                     x
    //  /   \     左旋轉(zhuǎn)         /  \
    // T1   x   --------->   node   T3
    //     / \              /   \
    //    T2 T3            T1   T2
    private Node leftRotate(Node node) {
        Node x = node.right;
        //左旋轉(zhuǎn)的過(guò)程
        node.right = x.left;
        x.left = node;

        x.color = node.color;
        node.color = RED;

        return x;
    }

    //     node                   x
    //    /   \     右旋轉(zhuǎn)       /  \
    //   x    T2   ------->   y   node
    //  / \                       /  \
    // y  T1                     T1  T2
    private Node rightRotate(Node node) {
        Node x = node.left;

        //右旋轉(zhuǎn)過(guò)程
        node.left = x.right;
        x.right = node;

        x.color = node.color;
        node.color = RED;

        return x;
    }

    /**
     * 顏色翻轉(zhuǎn)
     *
     * @param node
     */
    private void flipColors(Node node) {
        node.color = RED;
        node.left.color = BLACK;
        node.right.color = BLACK;
    }

    /**
     * 向紅黑樹(shù)中添加新的元素
     *
     * @param key
     * @param value
     */
    @Override
    public void add(K key, V value) {
        root = add(root, key, value);
        //最終根節(jié)點(diǎn)為黑色節(jié)點(diǎn)
        root.color = BLACK;
    }

    /**
     * 向以node為根節(jié)點(diǎn)的紅黑樹(shù)中插入元素(key, value)(遞歸算法)并返回插入新節(jié)點(diǎn)后紅黑樹(shù)的根節(jié)點(diǎn)
     *
     * @param node
     * @param key
     * @param value
     * @return
     */
    private Node add(Node node, K key, V value) {
        if (node == null) {
            size++;
            //默認(rèn)插入紅色節(jié)點(diǎn)
            return new Node(key, value);
        }
        if (key.compareTo(node.key) < 0) {
            node.left = add(node.left, key, value);
        } else if (key.compareTo(node.key) > 0) {
            node.right = add(node.right, key, value);
        } else {
            node.value = value;
        }
        if (isRed(node.right) && !isRed(node.left)) {
            node = leftRotate(node);
        }
        if (isRed(node.left) && isRed(node.left.left)) {
            node = rightRotate(node);
        }
        if (isRed(node.left) && isRed(node.right)) {
            flipColors(node);
        }
        return node;
    }

    @Override
    public V remove(K key) {
        throw new IllegalArgumentException("No remove in RedBlackTree!");
    }

    @Override
    public boolean contains(K key) {
        return getNode(root, key) != null;
    }

    @Override
    public V get(K key) {
        Node node = getNode(root, key);
        return node == null ? null : node.value;
    }

    @Override
    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            throw new IllegalArgumentException(key + " not exist!");
        }
        node.value = newValue;
    }

    @Override
    public int getSize() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.size == 0;
    }

    /**
     * 返回以node為根節(jié)點(diǎn)的二分搜索樹(shù)中key所在的節(jié)點(diǎn)
     *
     * @param node
     * @param key
     * @return
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }
        if (key.equals(node.key)) {
            return node;
        } else if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else {
            return getNode(node.right, key);
        }
    }

    private class Node {

        public K key;

        public V value;

        public Node left;

        public Node right;

        public boolean color;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.left = null;
            this.right = null;
            this.color = RED;
        }


    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容