紅黑樹(Red Black Tree)

1. 簡介

  • 紅黑樹(Red Black Tree) 是一種自平衡二叉查找樹,是二叉查找樹的變種之一。它是在1972年由Rudolf Bayer發明的,當時被稱爲平衡二叉B樹(symmetric binary B-trees)。後來,在1978年被 Leo J. GuibasRobert Sedgewick修改成現在的「紅黑樹」。 2008年 Robert Sedgewick 對其進行了改進,並命名爲 LLRBT(Left-leaning Red Black Tree 左傾紅黑樹)。左傾紅黑樹相比1978年的紅黑樹要簡單不少,實現的代碼量也少不少。Robert Sedgewick也是Algorithms(中文版叫《算法》)這本書的做者,在這本書中就講了基於2-3樹的左傾紅黑樹。
  • 如今的使用的工程代碼中的紅黑樹都是基於78年的算法,好比JDK中的TreeMap。其實紅黑樹就是2-3-4樹的具體實現,因此要想理解紅黑樹就得先理解2-3-4樹。而08年左傾紅黑樹則是基於2-3樹。

2. 定義

紅黑樹是2-3-4樹的實現,因此在講紅黑樹以前想講下2-3-4樹有助於理解紅黑樹。 由於紅黑樹是一棵自平衡二叉搜索樹,經過結點顏色改變和局部旋轉來維持平衡,因此除了一些會改變樹結構的操做以外,其餘的操做都和普通的二叉搜索樹相同。所以這裏就只講插入刪除操做。 由於我要用紅黑樹實現一個符號表,因此結點須要存儲鍵值對,並且實現的紅黑樹是基於2-3-4樹。java

2-3-4樹的定義

  • 2-3-4樹能夠存在三種類型結點。
  • 2-結點是一個結點有2條連接和1個鍵,其中兩條連接對應於二叉搜索樹中的左右連接。
  • 3-結點是一個結點有3條連接和2個鍵。
  • 4-結點是一個結點有4條連接和3個鍵。

一棵2-3-4樹

紅黑樹的定義

  1. 每一個結點都有顏色,不是黑色就是紅色。
  2. 根結點是黑色的。
  3. 空結點都是黑色的。
  4. 若是一個結點是紅色的,則與它相連的結點都只能是黑色的,也就是不能夠有兩個紅色結點相連。
  5. 每一個空結點到根結點的簡單路徑中所含的黑色結點數目相同。

一棵紅黑樹
經過觀察以上兩圖基本能看出二者的關係了

  • 第一張圖已經存在三種結點了,其中1和3都是2-結點,2和4構成一個3-結點,5和6和7構成一個4-結點。
  • 第二張圖則是第一張圖中2-3-4樹在紅黑樹的表現形式。 如今我總結一下2-3-4樹中三種結點在紅黑樹中的表示:
  • 2-結點

2-結點

  • 3-結點

3-結點

3-結點

  • 4-結點

4-結點

3. 實現

實現部分的代碼用Javanode

結點的定義

每一個結點的類型是Node,裏面有5個字段。算法

private class Node {
        Key key;
        Value value;
        Node left;
        Node right;
        boolean color;

        public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }
複製代碼

紅黑樹的插入

當咱們想要在樹中插入一個新結點時,先在樹中搜索與插入結點鍵相同的結點。dom

  1. 若是找到該結點則直接修改對應的Value字段就完成了。
  2. 若是找不到該結點則建立一個新的結點並把這個新結點設置爲紅色(由於插入一個紅色結點不會改變紅黑樹的性質5),隨後插到對應樹底部對應的結點下。然而插入樹底部對應結點下,那這個對應的結點有三種可能,分別是上面說到的2-,3-,4-結點。
    • 若是插到2-結點下,因爲2-結點是黑色結點則不會破壞紅黑樹的任何性質,因此不用作任何操做就完成了。性能

    • 若是插到3-結點下,從上面3-結點的圖看,3-結點有三個位置能夠插入。測試

      • 若是插入黑色結點的位置下則變成4-結點也不用作任何操做就完成了。ui

      • 若是插到3-結點的紅色結點下,則破壞了紅黑樹的性質4。以下圖新插入的0003結點,由於插入位置在右邊,則須要對0001作一個左旋操做: this

        左旋

      • 若是插入位置在左邊,以下圖新插入的0002結點。則須要對插入結點的父節點作一個右旋操做,再對0001作一個左旋操做: spa

        先右旋再左旋

    • 不管插到4-結點的哪一個地方都會破壞性質4,這時只要將4-結點分解爲兩個2-結點並將中間結點往上傳給父結點。以下圖新插入的0004結點: 3d

      分解4-結點

紅黑樹的刪除

  1. 首先要刪除一個結點的話,首先應該找到該結點
  2. 爲了刪除結點時不破壞紅黑樹性質5的狀況下,須要保證刪除的結點爲紅色而且在查找的過程當中也保證性質5不被破壞,即在自頂向下搜索要刪除結點過程當中,保證當前結點是紅色的。若是當前結點不是要刪除的結點,在接着再往下搜索時判斷下一個結點的顏色,定義下一個結點爲左結點,(下個結點爲右結點的狀況與左結點相反):
    • 若是下個結點是紅色或者爲空,則不須要作任何操做
    • 若是下個結點爲黑色且下個結點的兄弟結點也是黑色的話,直接將當前結點和兩個子結點合併爲一個4-結點
    • 若是下個結點爲黑色而下個結點的兄弟結點是紅色的話,直接對當前結點作一個左旋操做。
  3. 當找到要刪除的結點(爲紅色),就跟二叉搜索樹同樣刪除結點。
  4. 當自頂向下刪除完結點後,須要向上回溯消除全部破壞紅黑樹性質4的狀況,這一步經過平衡操做來實現。

代碼實現

import java.util.*;

public class RBTree <Key extends Comparable<Key>, Value>{
    private class Node {
        Key key;
        Value value;
        Node left;
        Node right;
        boolean color;

        public Node(Key key, Value value, Node left, Node right, boolean color) {
            this.key = key;
            this.value = value;
            this.left = left;
            this.right = right;
            this.color = color;
        }
    }

    private static final boolean RED = true;
    private static final boolean BLACK = false;
    private int size;
    private Node root;


    public boolean isEmpty() {
        return root == null;
    }

    private boolean isRed(Node node) {
        return node != null && node.color;
    }

    //顏色轉換
    private void flipColors(Node h) {
        h.color = !h.color;
        h.left.color = !h.left.color;
        h.right.color = !h.right.color;
    }

    //左旋
    private Node rotationLeft(Node node) {
        Node x = node.right;
        node.right = x.left;
        x.left = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    //右旋
    private Node rotationRight(Node node) {
        Node x = node.left;
        node.left = x.right;
        x.right = node;
        x.color = node.color;
        node.color = RED;
        return x;
    }

    //平衡操做
    private Node balance(Node node) {

        if (isRed(node.left) && isRed(node.right) && !isRed(node)) {
            if ((isRed(node.left.left) || isRed(node.left.right) || isRed(node.right.left) || isRed(node.right.right)))
                flipColors(node);
        }
        else {
            if (isRed(node.left)){
                if (isRed(node.left.right))
                    node.left = rotationLeft(node.left);
                if (isRed(node.left) && isRed(node.left.left))
                    node = rotationRight(node);
            }else if (isRed(node.right)){
                if (isRed(node.right) && isRed(node.right.left))
                    node.right = rotationRight(node.right);

                if (isRed(node.right) && isRed(node.right.right))
                    node = rotationLeft(node);
            }

            if (isRed(node.left) && isRed(node.right) && !isRed(node)) {
                if ((isRed(node.left.left) || isRed(node.left.right) || isRed(node.right.left) || isRed(node.right.right)))
                    flipColors(node);
            }
        }
        return node;
    }

    private Node max(Node node) {
        if(node == null) {
            return null;
        } else {
            while(node.right != null) {
                node = node.right;
            }

            return node;
        }
    }

    private Node min(Node node) {
        if(node == null) {
            return null;
        } else {
            while(node.left != null) {
                node = node.left;
            }

            return node;
        }
    }

    public Value max() {
        return root == null ? null : max(root).value;
    }

    public Value min() {
        return root == null ? null : min(root).value;
    }


    //插入
    public void put(Key key, Value value) {
        root = put(key, value, root);
        root.color = BLACK;
    }

    private Node put(Key key, Value value, Node node) {
        if(node == null) {
            ++size;
            return new Node(key, value, null, null, RED);
        } else {
            int cmp = key.compareTo(node.key);
            if(cmp < 0) {
                node.left = put(key, value, node.left);
            } else if (cmp > 0){
                node.right = put(key, value, node.right);
            }else{
                node.value = value;
            }

           return balance(node);
        }
    }

    public void deleteMin(){
        if (!isEmpty()){
            root.color = RED;

            root = deleteMin(root);
            --size;
            if (!isEmpty())
                root.color = BLACK;
        }
    }

    private Node deleteMin(Node node){
        if (node.left == null){
            return node.right;
        }

        if (!isRed(node.left)) {
            if(!isRed(node.left) && !isRed(node.right))
                flipColors(node);
            else
                node = rotationLeft(node);
        }

        node.left = deleteMin(node.left);

        return balance(node);

    }

    public void deleteMax(){
        if (!isEmpty()){
            root.color = RED;

            root = deleteMax(root);
            --size;
            if (!isEmpty())
                root.color = BLACK;
        }
    }

    private Node deleteMax(Node node){
        if (node.right == null){
            return node.left;
        }

        if (!isRed(node.right)) {
            if(!isRed(node.left) && !isRed(node.right))
                flipColors(node);
            else
                node = rotationRight(node);
        }

        node.right = deleteMax(node.right);

        return balance(node);

    }

    //刪除
    public void delete(Key key){
        if (!isEmpty()){
            root.color = RED;

            root = delete(key, root);

            if (!isEmpty())
                root.color = BLACK;
        }
    }

    private Node delete(Key key, Node node){
        if (node == null)
            return null;

        int cmp = key.compareTo(node.key);

        if (cmp < 0){
            if (node.left != null && !isRed(node.left)) {
                if(!isRed(node.right))
                    flipColors(node);
                else
                    node = rotationLeft(node);
            }

            node.left = delete(key, node.left);
        }else if (cmp > 0){
            if (node.right != null && !isRed(node.right)) {
                if(!isRed(node.left))
                    flipColors(node);
                else
                    node = rotationRight(node);
            }

            node.right = delete(key, node.right);
        }else {
            --size;
            if (node.left == null)
                return node.right;

            if (node.right == null)
                return node.left;

            Node x = min(node.right);
            node.key = x.key;
            node.value = x.value;

            node = delete(x.key, node);
        }

        return balance(node);
    }



    //判斷樹是否爲一棵紅黑樹
    public boolean isRBTree() {
        return isRBTree(root);
    }

    public boolean isRBTree(Node node) {
        if(node == null) {
            return true;
        } else if(node.color == RED) {
            return false;
        } else {
            Node x = node;
            int count = 0;

            for(; x != null; x = x.left) {
                if(x.color == BLACK) {
                    ++count;
                }
            }

            return isRBTree(node, count, 0);
        }
    }

    private boolean isRBTree(Node node, int count, int k) {
        if(node == null) {
            return count == k;
        } else if((isRed(node.left) && isRed(node.left.left))
                ||(isRed(node.left) && isRed(node.left.right))
                ||(isRed(node.right) && isRed(node.right.right))
                ||(isRed(node.right) && isRed(node.right.left))) {
            return false;
        } else {
            if(node.color == BLACK) {
                ++k;
            }
            return node.left == null && node.right == null ? k == count:isRBTree(node.left, count, k) && isRBTree(node.right, count, k);
        }
    }

    //樹的中序遍歷
    public void inTraverse(){
        inTraverse(root);
    }

    private void inTraverse(Node node){
        if (node == null)
            return;
        inTraverse(node.left);
        System.out.print(node.key + " ");
        inTraverse(node.right);
    }

    //測試
    public static void main(String[] args) {
        int n = 3000, a;
        Random random = new Random();
        RBTree<Integer, String> rbt = new RBTree();

        for (int i = 1; i <= n; ++i) {
            a = random.nextInt(50000);
            rbt.put(a, "naoko");
        }

        for (int i = 0; i < 1500; ++i) {
            rbt.delete(i);
        }

        if (!rbt.isRBTree()) {
            System.out.println("不是紅黑樹");
            return;
        }

        rbt.inTraverse();

        System.out.print("是紅黑樹");
    }

}

複製代碼

算法複雜度

紅黑樹和AVL樹相似,都是在進行插入和刪除操做時經過特定操做保持樹的平衡,從而得到較高的查找性能。不一樣的是紅黑樹並非向AVL樹那樣追求完美平衡,而是黑色平衡,即從根結點到任意一個空結點的簡單路徑上黑色結點數都相同。由於一棵紅黑樹的高度最高不超過2lg(N+1),所以其查找時間複雜度也是O(lgN)級別的。而對於插入和刪除操做產生不平衡狀況都會在3次旋轉以內快速解決,因此複雜度基本爲O(lgN)級別,也由於這一點紅黑樹的效率比AVL樹高。

最後

紅黑樹的插入和刪除操做都有自頂向下和自底向上兩種方法,其中自頂向下較爲容易,個人刪除操做實現屬於自頂向下的方法。在JDK中的TreeMap中插入和刪除就用了自底向上的方法。

相關文章
相關標籤/搜索