算法笔记:红黑树

红黑树,一种平衡二叉树,最为著名的应用就是C++ STL中的map,是有序集合最为理想的存储方式之一。除了二叉树所具备的属性以后,红黑树中每一个节点多了一个“颜色”属性,能够是红色或者是黑色。一棵红黑树应该知足一下的性质:c++

  1. 每一个节点是红色或者黑色的;
  2. 根节点是黑色的;
  3. 每一个叶节点nil是黑色的(使用哨兵节点在删除调整时能够方便很多);
  4. 若是一个节点是红色的,那么它的两个子节点是黑色的;
  5. 对于每个节点,到后代全部叶节点所通过的黑色节点数目相同。

根据定义,能够写出红黑树节点的数据结构:git

struct Node {
    enum Color { RED, BLACK };
    Color _color;
    Key _key;
    Value _value;
    Node *_parent, *_left, *_right;
    Node(): _color(BLACK) {}
    Node(const Key &key, const Value &value, Node *left, Node *right, Node *parent):
        _key(key), _value(value), _color(RED), _left(left), _right(right), _parent(parent) {}
};
复制代码

本文省略了拷贝、析构、赋值等操做,完整源代码放在Gist上。github

旋转

想要红黑树维持着平衡,就须要在插入元素和删除元素的过程当中不断对结构进行调整。其中,最基础的操做就是左旋右旋数据结构

以左旋为例,操做能够分为三步:ui

  1. 将Q的左子节点变成P的右子节点;
  2. 将P变成Q的左子节点;
  3. 将Q变成当前子树的根节点。
void leftRotate(Node *x) {
    Node *y = x->_right;
    // remove y->left to x->right
    x->_right = y->_left;
    if (x->_right != nil)
        x->_right->_parent = x;
    // remove y up
    y->_parent = x->_parent;
    if (x->_parent == nil)
        root = y;
    else if (x->_parent->_left == x)
        x->_parent->_left = y;
    else
        x->_parent->_right = y;
    // remove x down
    x->_parent = y;
    y->_left = x;
}
复制代码

插入

先污染,后治理。首选按照二叉树的插入方法先将节点插入二叉树,而后再对红黑树进行调整。spa

void insert(Node *nptr) {
    Node *it = root, *p = root;
    // find insert position
    while (it != nil) {
        p = it;
        if (nptr->_key < it->_key)
            it = it->_left;
        else if (nptr->_key > it->_key)
            it = it->_right;
        else {
            // find target key-value
            it->_value = nptr->_value;
            return;
        }
    }
    // insert
    nptr->_parent = p;
    if (p == nil)
        root = nptr;
    else if (nptr->_key < p->_key)
        p->_left = nptr;
    else
        p->_right = nptr;
    // fixup
    insertFixup(nptr);
}
复制代码

在完成插入以后,将面临六种状况,因为存在左右对称的状况,实际上只须要考虑三种状况。3d

状况1时,调整目标节点B的父节点和叔节点都是红节点,祖父节点为黑节点,咱们须要将父节点和叔节点的颜色改为黑色,祖父节点设为红节点,并将调整目标设为祖父节点。code

状况2状况3中,新插入节点的父节点为红节点,祖父节点为黑节点,而且叔节点为黑节点。首选须要把状况2转换成状况3,让B成为黑节点,A和C为B的红色子节点,并将调整目标设为A。每每在处理完这两种状况后,红黑树完成了调整。cdn

因为NIL也算是黑色节点,因此还须要定义一个获取节点颜色的宏。blog

调整过程是自下而上的,一个插入的新节点的颜色是红色的,每次调整的目标是使每一个子树保持红黑树的性质。对于状况2和3来讲,调整结束后子树树根为黑色,对总体的性质不会形成影响。而在状况1中,子树树根为红色,对总体的性质形成了影响,须要继续调整,这就是循环条件的意义

void insertFixup(Node *ptr) {
    while (ptr->_parent->_color == Node::RED) {
        if (ptr->_parent == ptr->_parent->_parent->_left) {
            Node *y = ptr->_parent->_parent->_right;
            // case 1
            if (y->_color == Node::RED) {
                ptr->_parent->_color = Node::BLACK;
                y->_color = Node::BLACK;
                ptr->_parent->_parent->_color = Node::RED;
                ptr = ptr->_parent->_parent;
            } else {
                // case 2: switch case 2 to case 3
                if (ptr == ptr->_parent->_right) {
                    ptr = ptr->_parent;
                    leftRotate(ptr);
                }
                // case 3
                ptr->_parent->_color = Node::BLACK;
                ptr->_parent->_parent->_color = Node::RED;
                rightRotate(ptr->_parent->_parent);
            }
        } else {
            // with 'left' and 'right' exchanged
            Node *y = ptr->_parent->_parent->_left;
            if (y->_color == Node::RED) {
                ptr->_parent->_color = Node::BLACK;
                y->_color = Node::BLACK;
                ptr->_parent->_parent->_color = Node::RED;
                ptr = ptr->_parent->_parent;
            } else {
                if (ptr == ptr->_parent->_left) {
                    ptr = ptr->_parent;
                    rightRotate(ptr);
                }
                ptr->_parent->_color = Node::BLACK;
                ptr->_parent->_parent->_color = Node::RED;
                leftRotate(ptr->_parent->_parent);
            }
        }
    }
    root->_color = Node::BLACK;
}
复制代码

删除

删除的过程比插入过程复杂不少。与二叉树同样,咱们须要一个替换操做,将子树u替换成子树v。

void transplant(shared_ptr<Node> u, shared_ptr<Node> v) {
    if (u->_parent == nil)
        root = v;
    else if (u == u->_parent->_left)
        u->_parent->_left = v;
    else
        u->_parent->_right = v;
    v->_parent = u->_parent;
}
复制代码

删除的过程和二叉树相似,多了一些处理哨兵、记录颜色的过程。

void remove(Node *ptr) {
        Node *y = ptr, *x;
        int y_original_color = y->_color;
        if (y->_left == nil) {
            x = ptr->_right;
            transplant(ptr, ptr->_right);
        } else if (y->_right == nil) {
            x = ptr->_left;
            transplant(ptr, ptr->_left);
        } else {
            y = min(ptr->_right);
            y_original_color = y->_color;
            x = y->_right;
            if (y->_parent == ptr)
                x->_parent = y; // change nil->_parent
            else {
                transplant(y, y->_right);
                y->_right = ptr->_right;
                y->_right->_parent = y;
            }
            transplant(ptr, y);
            y->_left = ptr->_left;
            y->_left->_parent = y;
            y->_color = ptr->_color;
        }
        if (y_original_color == Node::BLACK)
            deleteFixup(x);
    }
复制代码

首先考虑一下删除节点的时候,咱们对红黑树形成了什么样的影响。**若是删除一个红色节点,那么红黑树的性质并不会收到任何影响;若是删除的是一个黑色节点,那么意味着黑高相等的性质将不复存在,删除一个黑色节点。**那么和插入调整的思路相似,每次调整的目标是保持子树内的性质。

调整过程须要分左右对称的四种状况:

  • 状况1:有一个红色的兄弟节点,经过旋转和颜色调换,使红色节点到调整目标节点一侧来,变成状况二、三、4中的一种;
  • 状况2:兄弟节点的两个子节点全为黑,因而把兄弟节点设为红节点,这样一来,子树中的黑高是相等了,可是删除的黑节点并无被弥补,还须要继续往上调整;
  • 状况3:兄弟节点左红右黑,这个时候须要把红色节点调整到叔节点的右侧,变成状况4;
  • 状况4:兄弟节点右节点为红,这个时候须要把红节点调整到调整目标节点一侧来,用这个红色节点弥补删除的黑色节点。调整结束后,子树知足红黑树性质,结束调整过程。

而对于循环条件。应该怎么理解呢?若是调整过程到了根节点,那么就不存在某子树内黑高缺一的状况,能够结束循环。若是遇到了红节点,那么把这个红节点变成黑节点就能够解决黑高不等的状况。

void removeFixup(Node *ptr) {
    while (ptr != root && ptr->_color == Node::BLACK) {
        if (ptr == ptr->_parent->_left) {
            Node *w = ptr->_parent->_right;
            // case 1
            if (w->_color == Node::RED) {
                w->_color = Node::BLACK;
                ptr->_parent->_color = Node::RED;
                leftRotate(ptr->_parent);
                w = ptr->_parent->_right;
            }
            // case 2
            if (w->_left->_color == Node::BLACK && w->_right->_color == Node::BLACK) {
                w->_color = Node::RED;
                ptr = ptr->_parent;
            } else {
                // case 3
                if (w->_right->_color == Node::BLACK) {
                    w->_left->_color = Node::BLACK;
                    w->_color = Node::RED;
                    rightRotate(w);
                    w = ptr->_parent->_right;
                }
                // case 4
                w->_color = ptr->_parent->_color;
                ptr->_parent->_color = Node::BLACK;
                w->_right->_color = Node::BLACK;
                leftRotate(ptr->_parent);
                ptr = root;
            }
        } else {
            // with 'left' and 'right' exchanged
            Node *w = ptr->_parent->_left;
            if (w->_color == Node::RED) {
                w->_color = ptr->_parent->_color;
                ptr->_parent->_color = Node::RED;
                rightRotate(ptr->_parent);
                w = ptr->_parent->_left;
            }
            if (w->_left->_color == Node::BLACK && w->_right->_color == Node::BLACK) {
                w->_color = Node::RED;
                ptr = ptr->_parent;
            } else {
                if (w->_left->_color == Node::BLACK) {
                    w->_color = Node::RED;
                    w->_right->_color = Node::BLACK;
                    leftRotate(w);
                    w = ptr->_parent->_left;
                }
                w->_color = ptr->_parent->_color;
                w->_left->_color = Node::BLACK;
                ptr->_parent->_color = Node::BLACK;
                rightRotate(ptr->_parent);
                ptr = root;
            }
        }
    }
    ptr->_color = Node::BLACK;
}
复制代码

查找

节点颜色对于查找过程没有意义,红黑树查找过程和二叉树是同样的。

相关文章
相关标签/搜索