1. 程式人生 > >大名鼎鼎的紅黑樹,你get了麼?2-3樹 絕對平衡 右旋轉 左旋轉 顏色反轉

大名鼎鼎的紅黑樹,你get了麼?2-3樹 絕對平衡 右旋轉 左旋轉 顏色反轉

  前言

  11.1新的一月加油!這個購物狂歡的季節,一看,已囊中羞澀!趕緊來惡補一下紅黑樹和2-3樹吧!紅黑樹真的算是大名鼎鼎了吧?即使你不瞭解它,但一定聽過吧?下面跟隨我來揭開神祕的面紗吧!

  一、2-3樹

  1、搶了紅黑樹的光環?

  今天的主角是紅黑樹,是無疑的,主角光環在呢!那2-3樹又是什麼鬼呢?學習2-3樹不僅對理解紅黑樹有幫助,對理解B類樹,也是有巨大幫助的,所以學習2-3樹很必要!

  2、基本性質

  2-3樹滿足二分搜尋樹的基本性質,但節點可以存放一個元素或兩個元素!如下圖,就是2-3樹:

  

  說明:2-3樹一顆絕對平衡的樹(絕對平衡:對於任意一個節點,左右子樹高度相同)

  3、維護絕對平衡

  2-3樹在插入過程中如何維護絕對平衡呢?進行畫圖演示,實在有點不好畫,如下圖:

  

  說明:

  1、不能將新節點插入到空節點

    因為那樣如上圖,就不滿足絕對平衡了,所以可以將37和42合併,2-3支援3節點。

  2、不支援4節點,進行拆分

    再插入12時,也不能插入空節點,也要合併,但2-3樹不支援4節點,所以進行進行拆分。

  3、子節點達到3節點,合併到父節點

    再依次插入18、6,達到4節點,進行拆分,但不符合絕對平衡了怎麼辦?將12和37合併,就形成了最後3節點的圖了

  總結:講到這裡,應該對2-3樹如何維護絕對平衡,應該瞭解了吧?理解2-3樹,對於再理解紅黑樹,是非常有幫助的,其實,它們有等價性的,接下來會說明的。

  二、紅黑樹

  1、紅黑樹和2-3樹的等價性

  也想達到像2-3樹那樣的絕對平衡,但2-3樹的實現比較麻煩,所以產生了紅黑樹;那麼,紅黑樹和2-3樹有怎麼樣的等價性呢?如下圖:

  

  說明:紅黑樹最開始想用紅線區別b、c,但實現起來比較困難,然後用紅黑來表示節點,就比較好實現了!

  紅黑樹和2-3樹總體對比圖,可以參考一下:

  

  2、紅黑樹5個重要性質

  1、引自《演算法導論》

  紅黑樹有五個重要性質,引自演算法界一本聖潔《演算法導論》中的內容,如下:

  

  是不是看著有點暈,下面我進行解釋。

  2、5個重要性質

  1、每一個節點或者紅色的,或是黑色的

  2、根節點是黑色的

  3、每一個葉子節點(最後的空節點)是黑色的

  4、如果一個節點是紅色的,那麼它的孩子節點都是黑色的

  5、從任意節點到葉子節點,經過的黑色節點是一樣的

  解釋:最重要的性質是第五條,前4條在理解2-3樹之後,就很好理解了,第5條性質說明了:紅黑樹是保持“黑平衡”的二叉樹;

嚴格意義上來說,紅黑樹不是平衡二叉樹,最大高度:2logn,但是時間複雜度仍然是O(logn),因為2是常數,但比AVL樹查詢要稍微慢一些。

  三、紅黑樹新增元素

  紅黑樹新增元素,比較繁瑣,因為要保持上面的五個性質,要不然就不是紅黑樹了;

  1、保持根節點為節點

  紅黑樹的節點類也可以從二分搜尋樹上進行修改,但要新增“color”成員變數,來標註節點顏色,節點類如下:

template<typename Key, typename Value>
class RBTree {
private:
    static const bool RED = true;
    static const bool BLACK = false;

    struct Node {
        Key key;
        Value value;
        Node *left;
        Node *right;
        bool color;

        Node(Key key, Value value) {
            this->key = key;
            this->value = value;
            this->left = this->right = nullptr;
            color = RED;  //預設初始化為紅色
        }

        Node(Node *node) {
            this->key = node->key;
            this->value = node->value;
            this->left = node->left;
            this->right = node->right;
            this->color = node->color;
        }
    };

    Node *root;
    int size;
}

  因為紅黑樹性質1要求根節點為黑色,所以要保持根節點為黑色;

  2、左旋轉

  像AVL樹一樣,紅黑樹也需要左旋和右旋,如下圖就需要左旋轉,因為“紅色節點是左傾斜的”:

  

  說明:圖中黑色字型標識黑色節點,紅色表示紅色節點,並演示了旋轉過程,最後還要改變節點顏色。

  3、左旋轉程式碼實現

  程式碼如下:

Node *leftRotate(Node *node) {
        Node *x = node->right;
        node->right = x->left;
        x->left = node;

        x->color = node->color;
        node->color = RED;

        return x;
    }

  4、顏色反轉

  下面這種情況就需要顏色反轉,如下圖:

  

  5、顏色反轉程式碼實現

  程式碼如下:

void flipColors(Node *node) {
        node->color = RED;
        node->left->color = BLACK;
        node->right->color = BLACK;
    }

  6、右旋轉

  下面情況需要右旋轉,如下圖:

  

    旋轉之後,如下圖:

  

   7、右旋轉程式碼如下

  程式碼如下:

Node *rightRotate(Node *node) {
        Node *x = node->left;
        node->left = x->right;
        x->right = node;

        x->color = node->color;
        node->color = RED;

        return x;
    }

  8、總體流程圖

  

  9、總體程式碼

  總體程式碼如下,供參考和學習:

#ifndef RED_BLACK_TREE_RBTREE_H
#define RED_BLACK_TREE_RBTREE_H

#include <iostream>
#include <vector>

template<typename Key, typename Value>
class RBTree {
private:
    static const bool RED = true;
    static const bool BLACK = false;

    struct Node {
        Key key;
        Value value;
        Node *left;
        Node *right;
        bool color;

        Node(Key key, Value value) {
            this->key = key;
            this->value = value;
            this->left = this->right = nullptr;
            color = RED;
        }

        Node(Node *node) {
            this->key = node->key;
            this->value = node->value;
            this->left = node->left;
            this->right = node->right;
            this->color = node->color;
        }
    };

    Node *root;
    int size;

public:

    RBTree() {
        root = nullptr;
        size = 0;
    }

    ~RBTree() {
        destroy(root);
    }

    int getSize() {
        return size;
    }

    int isEmpty() {
        return size == 0;
    }

    bool isRed(Node *node) {
        if (node == nullptr) {
            return BLACK;
        }
        return node->color;
    }

    void add(Key key, Value value) {
        root = add(root, key, value);
        root->color = BLACK;
    }

    bool contains(Key key) {
        return getNode(root, key) != nullptr;
    }

    Value *get(Key key) {
        Node *node = getNode(root, key);
        return node == nullptr ? nullptr : &(node->value);
    }

    void set(Key key, Value newValue) {
        Node *node = getNode(root, key);
        if (node != nullptr) {
            node->value = newValue;
        }
    }

private:

    // 向以node為根的二叉搜尋樹中,插入節點(key, value)
    // 返回插入新節點後的二叉搜尋樹的根
    Node *add(Node *node, Key key, Value value) {
        if (node == nullptr) {
            size++;
            return new Node(key, value);
        }
        if (key == node->key) {
            node->value = value;
        } else if (key < node->key) {
            node->left = add(node->left, key, value);
        } else {
            node->right = add(node->right, key, 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;
    }

    // 在以node為根的二叉搜尋樹中查詢key所對應的Node
    Node *getNode(Node *node, Key key) {
        if (node == nullptr) {
            return nullptr;
        }
        if (key == node->key) {
            return node;
        } else if (key < node->key) {
            return getNode(node->left, key);
        } else {
            return getNode(node->right, key);
        }
    }

    void destroy(Node *node) {
        if (node != nullptr) {
            destroy(node->left);
            destroy(node->right);
            delete node;
            size--;
        }
    }

    Node *leftRotate(Node *node) {
        Node *x = node->right;
        node->right = x->left;
        x->left = node;

        x->color = node->color;
        node->color = RED;

        return x;
    }

    Node *rightRotate(Node *node) {
        Node *x = node->left;
        node->left = x->right;
        x->right = node;

        x->color = node->color;
        node->color = RED;

        return x;
    }

    void flipColors(Node *node) {
        node->color = RED;
        node->left->color = BLACK;
        node->right->color = BLACK;
    }
};

#endif //RED_BLACK_TREE_RBTREE_H
View Code

  總結  

  面試時99.9%不會讓手寫一下紅黑樹的新增過程,除非你面試演算法工程師,那就打擾了!主要理解紅黑樹的性質、左旋和右旋等。

  歡迎點贊和評論,感謝支援!