數據結構-紅黑樹和2-3樹

一. 紅黑樹的定義

  • 1.每個節點或者是紅色的,或者是黑色的
  • 2.根節點是黑色的(一棵空樹也是紅黑樹)。
  • 3.每個葉子節點(最後的空節點)是黑色的,定義空節點都是黑色的。
  • 4.如果一個節點是紅色的,那麼他的孩子節點都是黑色的。
  • 5.從任意一個節點到葉子節點,經過的黑色節點是一樣的。

學習紅黑樹之前,必須先學2-3樹

二. 2-3樹

學習2-3樹,不僅對於理解紅黑樹有幫助,對於B樹的數據結構也有幫助。

  • 2-3 樹滿足二分搜索樹的基本性質,2-3樹不是二叉樹。
  • 節點可以存放一個元素活着2個元素。
  • 2-3樹是一顆絕對平衡的樹:任何一個節點左右子樹的高度一定是相同的。
    在這裏插入圖片描述

如何維護2-3的平衡

j

三. 紅黑樹和2-3樹的等價性

在這裏插入圖片描述
在這裏插入圖片描述
紅黑樹是保持“黑”平衡的二叉樹,嚴格意義上,不是平衡二叉樹,最大高度爲2logN O(logN)
AVL的高度logN. 比AVL樹的高度高,查詢的時候比AVL慢一點. 如果只查詢的時候,AVL快一點。

五. 紅黑樹添加新元素

2-3樹中添加一個新元素: 或者添加晉2-節點,形成3-節點,或者添加進3-節點,暫時形成一個4-節點。
永遠添加紅色節點。然後維護紅黑樹。
添加節點之後,保持最終根節點的顏色爲黑色。

紅黑樹的左旋轉 、右旋轉、顏色翻轉

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

紅黑樹的添加

在這裏插入圖片描述

總結的流程如下:維護的時機和AVL樹一樣,添加節點後回溯向上維護。
在這裏插入圖片描述

六. 紅黑樹的Java簡單實現

package com.mk.coffee.test.dataStructure.redblackTree;

/**
 * 紅黑樹 《算法導論》:
 * <p>
 * 1.每個節點或者是紅色的,或者是黑色的
 * 2.根節點是黑色的(一棵空樹也是紅黑樹)。
 * 3.每個葉子節點(最後的空節點)是黑色的,定義空節點都是黑色的。
 * 4.如果一個節點是紅色的,那麼他的孩子節點都是黑色的。連接的可能2/3節點,都是黑色的。
 * 5.從任意一個節點到葉子節點,經過的黑色節點是一樣的。(2-3樹絕對平衡,經過的節點個數相同)
 * 黑色節點的右孩子節點一定是黑色的。
 * <p>
 * 《算法4》 紅黑樹的發明人 Robert Sedgewick  是 Donald Knuth的學生。
 * <p>
 * 紅黑樹與 2-3 樹的等價性
 * 理解2-3樹和紅黑樹之間的關係。
 * 紅色節點都是左孩子
 * 紅黑樹是保持"黑平衡"的二叉樹,嚴格意義來說不是平衡二叉樹。
 * 節點數 爲N,最大高度2logN,O(logN)
 * 紅黑樹比AVL 查找慢一點 ,紅黑樹2logN, AVLlogN
 * <p>
 * 添加的情況下,RBT好   AVL 28S  RBT20S
 * 查詢的情況下:AVL好
 * <p>
 * Java java.util.中的 TreeMap 和 TreeSet 基於紅黑樹
 * 紅黑樹的其他實現。
 * 總結:
 * 對於完全隨機的數據,普通的二分搜索樹就很好用!
 * 確定:極端的情況退化成鏈表(或者高度不平衡)
 * 對於查詢較多的情況下,AVL樹很好用!
 * 紅黑樹犧牲了平衡性(2logN的高度)
 * 統計性能更優(綜合增刪改查所有的操作,平均性能,紅黑樹性能最高)
 * <p>
 * <p>
 * 更多的問提
 * <p>
 * <p>
 * 伸展樹Splay Tree:局部性原理: 剛被訪問的內容下次高概率被再次訪問。
 *
 * @author makui
 * @date 2020/03/26
 */
public class RedBlackTree<K extends Comparable<K>, V> {
    //RED BLACk 代表紅黑色
    private static final boolean RED = true;
    private static final boolean BLACK = false;

    private class Node {
        public K key;
        public V value;
        public Node left, right;
        public boolean color;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.left = null;
            this.right = null;
            //默認是紅色 等價2-3樹添加節點永遠是先和節點融合,然後在做操作
            color = RED;
        }
    }

    private Node root;

    private int size;

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

    //傳入的Node左旋轉 返回旋轉後的根節點
    //左旋轉
    // T1 < y < T2 < x < T3 < z < T4
    private Node leftRotate(Node node) {
        Node x = node.right;
        //左旋轉
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;

        return x;
    }

    private Node rightRotate(Node node) {
        Node x = node.left;
        //右旋轉
        node.left = x.right;
        x.right = node;
        x.color = node.color;
        node.color = RED;

        return x;
    }

    /**
     * 向紅黑樹添加新的元素(key,value)
     * 保持根節點的顏色爲黑色
     *
     * @param key
     * @param value
     */
    public void add(K key, V value) {
        root = add(root, key, value);
        root.color = BLACK;
    }

    /**
     * 向node爲根的紅黑色中添加元素(key,value),遞歸算法
     * 返回插入新節點後紅黑樹的根
     *
     * @param node
     * @param key
     * @param value
     * @return
     */
    private Node add(Node node, K key, V value) {
        if (node == null) {
            size++;
            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)) {
            //轉換顏色
            flipColor(node);
        }
        return node;
    }

    private void flipColor(Node node) {
        node.color = true;
        node.left.color = false;
        node.right.color = false;

    }

    private boolean isRed(Node node) {
        return node.color;
    }

    /**
     * 返回以node爲根節點的二分搜索樹中,key所在的節點
     *
     * @param node
     * @param key
     * @return
     */
    private Node getNode(Node node, K key) {
        if (node == null) {
            return null;
        }

        if (key.compareTo(node.key) == 0) {
            return node;
        } else if (key.compareTo(node.key) < 0) {
            return getNode(node.left, key);
        } else {
            return getNode(node.right, key);
        }
    }

    public V remove(K key) {
        //todo 仿照BST  自己寫
        return null;
    }

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

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

    public void set(K key, V newValue) {
        Node node = getNode(root, key);
        if (node == null) {
            throw new IllegalArgumentException(key + "doesn't exist~");
        }
        node.value = newValue;
    }


    /**
     * 查找二分查找樹的最小元素
     *
     * @return
     */
    public V minimum() {
        if (size == 0) {
            throw new IllegalArgumentException("BST is empty!");
        }
        return minimum(root).value;
    }

    /**
     * 最小值以node爲根的二分搜索樹
     *
     * @param node
     * @return
     */
    private Node minimum(Node node) {
        if (node.left == null) {
            return node;
        }
        return minimum(node.left);
    }

    /**
     * 查找二分查找樹的最大元素
     *
     * @return
     */
    public V maximum() {
        if (size == 0) {
            throw new IllegalArgumentException("BST is empty!");
        }
        return maximum(root).value;
    }

    /**
     * 最大值以node爲根的二分搜索樹
     *
     * @param node
     * @return
     */
    private Node maximum(Node node) {
        if (node.right == null) {
            return node;
        }
        return minimum(node.right);
    }


    public int getSize() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }
}

七. 紅黑樹和AVL對比

如果插入一個node引起了樹的不平衡,AVL和RB-Tree都是最多只需要2次旋轉操作,即兩者都是O(1);但是在刪除node引起樹的不平衡時,最壞情況下,AVL需要維護從被刪node到root這條路徑上所有node的平衡性,因此需要旋轉的量級O(logN),而RB-Tree最多隻需3次旋轉(左旋轉,右旋轉,顏色翻轉),只需要O(1)的複雜度。
其次,AVL的結構相較RB-Tree來說更爲平衡,在插入和刪除node更容易引起Tree的unbalance,因此在大量數據需要插入或者刪除時,AVL需要rebalance的頻率會更高。因此,RB-Tree在需要大量插入和刪除node的場景下,效率更高。自然,由於AVL高度平衡,因此AVL的search效率更高。
map的實現只是折衷了兩者在search、insert以及delete下的效率。總體來說,RB-tree的統計性能是高於AVL的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章