C++红黑树

1,433 阅读10分钟

二分查找树BST的平衡问题

  • 理想状况:插入、删除、查找时间代价为O(logn )
  • 输入9,4,2,6,7,15,12,21
  • 输入2,4, 6,7, 9, 12,15, 21
  • 变成单链了

定义(red-black tree, 简称RB-tree)

红黑树:平衡的 扩充 二叉搜索树

  • 颜色特征:结点是 或“黑色”; “红色”
  • 根特征 :根结点永远是“黑色”的;
  • 外部特征:扩充外部叶结点都是空的“黑色”结点;
  • 内部特征:“红色”结点的两个子结点都是“黑色”的,不允许两个连续的红色结点;
  • 深度特征:任何结点到其子孙外部结点的每条简单路径都包含相同数目的“黑色”结点

红黑树的阶

结点X的阶(rank,也称“黑色高度”)

  • 从该结点到外部结点的黑色结点数量,不包括 X 结点本身,包括叶结点
    • 外部结点的阶是零
    • 根的阶称为该树的阶

红黑树的性质

  1. 红黑树是满二叉树,空叶结点也看作结点
  2. 阶为 k 的红黑树路径长度最短是 k,最长是 2k (从根到叶的简单路径长度)
  3. 阶为 k 的红黑树树高最小是 k+1,最高是 2k+1
  4. 阶为 k 的红黑树的内部结点最少是一棵完全满二叉树,内部结点数最少是 2^k-1
  5. n 个内部结点的红黑树树高,最大是 2log2(n+1)+1

插入算法(参考这里的图示算法)

  • 先调用 BST 的插入算法

    • 把新记录着色为红色
    • 若父结点是黑色,则算法结束
  • 否则,双红调整

  • 共分为四种情形

    1. 新节点N位于树的根上,没有父节点。在这种情形下,我们把它重绘为黑色以满足性质2。
     void insert_case1(node *n){
     if(n->parent == NULL)
         n->color = BLACK;
     else
         insert_case2 (n);
     }
    
    1. 新节点的父节点P是黑色,所以性质4没有失效(新节点是红色的)。直接插入,结束。
     void insert_case2(node *n){
     if(n->parent->color == BLACK)
         return; /* 树仍旧有效*/
     else
         insert_case3 (n);
    }
    
    1. 情形3:如果父节点P和叔父节点U二者都是红色,(此时新插入节点N做为P的左子节点或右子节点都属于情形3,这里右图仅显示N做为P左子的情形)则我们可以将它们两个重绘为黑色并重绘祖父节点G为红色(用来保持性质5)。现在我们的新节点N有了一个黑色的父节点P。因为通过父节点P或叔父节点U的任何路径都必定通过祖父节点G,在这些路径上的黑节点数目没有改变。但是,红色的祖父节点G可能是根节点,这就违反了性质2,也有可能祖父节点G的父节点是红色的,这就违反了性质4。为了解决这个问题,我们在祖父节点G上递归地进行情形1的整个过程。(把G当成是新加入的节点进行各种情形的检查)
     void insert_case3(node *n){
     if(uncle(n) != NULL && uncle (n)->color == RED) {
         n->parent->color = BLACK;
         uncle (n)->color = BLACK;
         grandparent (n)->color = RED;
         insert_case1(grandparent(n));
     }
     else
         insert_case4 (n);
    }
    
    1. 情形4:父节点P是红色而叔父节点U是黑色或缺少,并且新节点N是其父节点P的右子节点而父节点P又是其父节点的左子节点。在这种情形下,我们进行一次左旋转调换新节点和其父节点的角色;接着,我们按情形5处理以前的父节点P以解决仍然失效的性质4。注意这个改变会导致某些路径通过它们以前不通过的新节点N(比如图中1号叶子节点)或不通过节点P(比如图中3号叶子节点),但由于这两个节点都是红色的,所以性质5仍有效。
     void insert_case4(node *n){
     if(n == n->parent->right && n->parent == grandparent(n)->left) {
         rotate_left(n->parent);
         n = n->left;
     } else if(n == n->parent->left && n->parent == grandparent(n)->right) {
         rotate_right(n->parent);
         n = n->right;
     }
     insert_case5 (p);
     }
    
    1. 情形5:父节点P是红色而叔父节点U是黑色或缺少,新节点N是其父节点的左子节点,而父节点P又是其父节点G的左子节点。在这种情形下,我们进行针对祖父节点G的一次右旋转;在旋转产生的树中,以前的父节点P现在是新节点N和以前的祖父节点G的父节点。我们知道以前的祖父节点G是黑色,否则父节点P就不可能是红色(如果P和G都是红色就违反了性质4,所以G必须是黑色)。我们切换以前的父节点P和祖父节点G的颜色,结果的树满足性质4。性质5也仍然保持满足,因为通过这三个节点中任何一个的所有路径以前都通过祖父节点G,现在它们都通过以前的父节点P。在各自的情形下,这都是三个节点中唯一的黑色节点。
 void insert_case5(node *n){
     n->parent->color = BLACK;
     grandparent (n)->color = RED;
     if(n == n->parent->left && n->parent == grandparent(n)->left) {
         rotate_right(grandparent(n));
     } else {
         /* Here, n == n->parent->right && n->parent == grandparent (n)->right */
         rotate_left(grandparent(n));
     }
 }

删除算法

  • 先调用 BST 的删除算法
    • 待删除的结点有一个以上的外部空指针,则直接删除
    • 否则在右子树中找到其后继结点进行值交换(着色不变)删除
  • v 是被删除的内结点, w 是被删外结点, X 是 w 的兄弟
    • 如果v或者 X 是红色, 则把X标记为黑色即可
    • 否则, X 需要标记为双黑, 根据其兄弟结点 C 进行重构调整

根据双黑 X 的兄弟 C 进行调整

假设X是左子结点(若X为右孩子,则对称)

  • 情况 1: C 是黑色,且子结点有红色
    • 重构,完成操作
  • 情况 2:C 是黑色, 且有两个黑子结点
    • 换色
    • 父结点 B 原为黑色,可能需要从 B 继续向上调整
  • 情况3:C是红色
    • 转换状态
    • C 转为父结点,调整为情况 1 或 2 继续处理

C++ 代码

#define BLACK 1
#define RED 0
#include <iostream>

using namespace std;

class bst {
private:

    struct Node {
        int value;
        bool color;
        Node *leftTree, *rightTree, *parent;

        Node() : value(0), color(RED), leftTree(NULL), rightTree(NULL), parent(NULL) { }        

        Node* grandparent() {
            if(parent == NULL){
                return NULL;
            }
            return parent->parent;
        }

        Node* uncle() {
            if(grandparent() == NULL) {
                return NULL;
            }
            if(parent == grandparent()->rightTree)
                return grandparent()->leftTree;
            else
                return grandparent()->rightTree;
        }

        Node* sibling() {
            if(parent->leftTree == this)
                return parent->rightTree;
            else
                return parent->leftTree;
        }
    };

    void rotate_right(Node *p){
        Node *gp = p->grandparent();
        Node *fa = p->parent;
        Node *y = p->rightTree;

        fa->leftTree = y;

        if(y != NIL)
            y->parent = fa;
        p->rightTree = fa;
        fa->parent = p;

        if(root == fa)
            root = p;
        p->parent = gp;

        if(gp != NULL){
            if(gp->leftTree == fa)
                gp->leftTree = p;
            else
                gp->rightTree = p;
        }

    }

    void rotate_left(Node *p){
        if(p->parent == NULL){
            root = p;
            return;
        }
        Node *gp = p->grandparent();
        Node *fa = p->parent;
        Node *y = p->leftTree;

        fa->rightTree = y;

        if(y != NIL)
            y->parent = fa;
        p->leftTree = fa;
        fa->parent = p;

        if(root == fa)
            root = p;
        p->parent = gp;

        if(gp != NULL){
            if(gp->leftTree == fa)
                gp->leftTree = p;
            else
                gp->rightTree = p;
        }
    }

    void inorder(Node *p){
        if(p == NIL)
            return;

        if(p->leftTree)
            inorder(p->leftTree);

        cout << p->value << " ";
                
        if(p->rightTree)
            inorder(p->rightTree);
    }

    string outputColor (bool color) {
        return color ? "BLACK" : "RED";
    }

    Node* getSmallestChild(Node *p){
        if(p->leftTree == NIL)
            return p;
        return getSmallestChild(p->leftTree);
    }

    bool delete_child(Node *p, int data){
        if(p->value > data){
            if(p->leftTree == NIL){
                return false;
            }
            return delete_child(p->leftTree, data);
        } else if(p->value < data){
            if(p->rightTree == NIL){
                return false;
            }
            return delete_child(p->rightTree, data);
        } else if(p->value == data){
            if(p->rightTree == NIL){
                delete_one_child (p);
                return true;
            }
            Node *smallest = getSmallestChild(p->rightTree);
            swap(p->value, smallest->value);
            delete_one_child (smallest);

            return true;
        }else{
           return false;
         }
    }

    void delete_one_child(Node *p){
        Node *child = p->leftTree == NIL ? p->rightTree : p->leftTree;
        if(p->parent == NULL && p->leftTree == NIL && p->rightTree == NIL){
            p = NULL;
            root = p;
            return;
        }
        
        if(p->parent == NULL){
            delete  p;
            child->parent = NULL;
            root = child;
            root->color = BLACK;
            return;
        }
        
        if(p->parent->leftTree == p){
            p->parent->leftTree = child;
        } else {
            p->parent->rightTree = child;
        }
        child->parent = p->parent;

        if(p->color == BLACK){
            if(child->color == RED){
                child->color = BLACK;
            } else
                delete_case (child);
        }

        delete p;
    }

    void delete_case(Node *p){
        if(p->parent == NULL){
            p->color = BLACK;
            return;
        }
        if(p->sibling()->color == RED) {
            p->parent->color = RED;
            p->sibling()->color = BLACK;
            if(p == p->parent->leftTree)
                rotate_left(p->sibling());
            else
                rotate_right(p->sibling());
        }
        if(p->parent->color == BLACK && p->sibling()->color == BLACK
                && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {
            p->sibling()->color = RED;
            delete_case(p->parent);
        } else if(p->parent->color == RED && p->sibling()->color == BLACK
                && p->sibling()->leftTree->color == BLACK && p->sibling()->rightTree->color == BLACK) {
            p->sibling()->color = RED;
            p->parent->color = BLACK;
        } else {
            if(p->sibling()->color == BLACK) {
                if(p == p->parent->leftTree && p->sibling()->leftTree->color == RED
                        && p->sibling()->rightTree->color == BLACK) {
                    p->sibling()->color = RED;
                    p->sibling()->leftTree->color = BLACK;
                    rotate_right(p->sibling()->leftTree);
                } else if(p == p->parent->rightTree && p->sibling()->leftTree->color == BLACK
                        && p->sibling()->rightTree->color == RED) {
                    p->sibling()->color = RED;
                    p->sibling()->rightTree->color = BLACK;
                    rotate_left(p->sibling()->rightTree);
                }
            }
            p->sibling()->color = p->parent->color;
            p->parent->color = BLACK;
            if(p == p->parent->leftTree){
                p->sibling()->rightTree->color = BLACK;
                rotate_left(p->sibling());
            } else {
                p->sibling()->leftTree->color = BLACK;
                rotate_right(p->sibling());
            }
        }
    }

    void insert(Node *p, int data){
        if(p->value >= data){
            if(p->leftTree != NIL)
                insert(p->leftTree, data);
            else {
                Node *tmp = new Node();
                tmp->value = data;
                tmp->leftTree = tmp->rightTree = NIL;
                tmp->parent = p;
                p->leftTree = tmp;
                insert_case (tmp);
            }
        } else {
            if(p->rightTree != NIL)
                insert(p->rightTree, data);
            else {
                Node *tmp = new Node();
                tmp->value = data;
                tmp->leftTree = tmp->rightTree = NIL;
                tmp->parent = p;
                p->rightTree = tmp;
                insert_case (tmp);
            }
        }
    }

    void insert_case(Node *p){
        if(p->parent == NULL){
            root = p;
            p->color = BLACK;
            return;
        }
        if(p->parent->color == RED){
            if(p->uncle()->color == RED) {
                p->parent->color = p->uncle()->color = BLACK;
                p->grandparent()->color = RED;
                insert_case(p->grandparent());
            } else {
                if(p->parent->rightTree == p && p->grandparent()->leftTree == p->parent) {
                    rotate_left (p);
                    rotate_right (p);
                    p->color = BLACK;
                    p->leftTree->color = p->rightTree->color = RED;
                } else if(p->parent->leftTree == p && p->grandparent()->rightTree == p->parent) {
                    rotate_right (p);
                    rotate_left (p);
                    p->color = BLACK;
                    p->leftTree->color = p->rightTree->color = RED;
                } else if(p->parent->leftTree == p && p->grandparent()->leftTree == p->parent) {
                    p->parent->color = BLACK;
                    p->grandparent()->color = RED;
                    rotate_right(p->parent);
                } else if(p->parent->rightTree == p && p->grandparent()->rightTree == p->parent) {
                    p->parent->color = BLACK;
                    p->grandparent()->color = RED;
                    rotate_left(p->parent);
                }
            }
        }
    }

    void DeleteTree(Node *p){
        if(!p || p == NIL){
            return;
        }
        DeleteTree(p->leftTree);
        DeleteTree(p->rightTree);
        delete p;
    }
public:

    bst() {
        NIL = new Node();
        NIL->color = BLACK;
        root = NULL;
    }

    ~bst() {
        if (root)
            DeleteTree (root);
        delete NIL;
    }

    void inorder() {
        if(root == NULL)
            return;
        inorder (root);
        cout << endl;
    }

    void insert (int x) {
        if(root == NULL){
            root = new Node();
            root->color = BLACK;
            root->leftTree = root->rightTree = NIL;
            root->value = x;
        } else {
            insert(root, x);
        }
    }

    bool delete_value (int data) {
        return delete_child(root, data);
    }
private:
    Node *root, *NIL;
};

红黑树操作的代价

  • 检索平均和最差时间都是O(log2 n)
    • 自根向下查找
    • 代价就是树高
  • 插入、删除平均和最差时间都是O(log2 n)
    • 先检索到待操作的位置
    • 自底向根的方向调整,局部的调整操作
  • 红黑树的持久版本对每次插入或删除需要是O(log2 n)的空间

应用场景

广泛用在C++的STL中。如map和set都是用红黑树实现的。适合小数据量,在内存中使用。