红黑树详细讲解(多文整合)

来源:互联网 发布:豌豆公主app数据 编辑:程序博客网 时间:2024/06/05 19:11

  • 简介
  • 基本操作
    • 1 基本旋转操作
    • 2 求节点的后继节点
  • 插入
  • 删除
  • 代码
  • 个人总结


本文是笔者在翻了网上各篇关于红黑树的文章,最终学懂红黑树后,将令笔者有所收获的几篇文章,整合在一起,并加上自身的修改和总结,希望能够让读者更容易理解红黑树。其中,基本操作、插入和部分简介转载自http://blog.chinaunix.net/uid-27767798-id-3339483.html,删除转载自http://blog.csdn.net/spch2008/article/details/9338923,部分简介转载自http://blog.csdn.net/eric491179912/article/details/6179908 。 代码中的红黑树类转载自一篇转载文,原作者不详。笔者已在OJ上通过了该代码。(笔者自己也写了个红黑树类并通过了OJ,但是没它详细,就贴了这篇 ^-^)
对原作者一并表示感谢!


1. 简介

红黑树是一种二叉查找树,它是在1972年由Rudolf Bayer发明的,它的性能优于平衡2叉树(avl树),因为avl树过分追求平衡,avl树要求任何节点的左右子树高度之差不能大于1,而红黑树做到的是任何节点的左右子树高度差不会超过2倍(左子树的高度不会大于右子树高度的2倍,或者右子树的高度不会大于左子树的高度的2倍),由此看出avl树如果要保持平衡需要付出更多的旋转(左旋,右旋),avl更平衡意味着avl树比红黑树的高度更低,查询时更快一些,但是过多旋转的时间代价大于查询带来的优势。红黑树的应用:jdk中的treeMap,内核中CFS调度根据vruntime(虚拟运行时间),来为进程建立红黑树结构,等等

红黑树满足以下5个性质:

  1. 每个结点的颜色只能是红色或黑色。
  2. 根结点是黑色的。
  3. 每个叶子结点都带有两个空的黑色结点(被称为黑哨兵),如果一个结点n的只有一个左孩子,那么n的右孩子是一个黑哨兵;如果结点n只有一个右孩子,那么n的左孩子是一个黑哨兵。
  4. 如果一个结点是红的,则它的两个儿子都是黑的。也就是说在一条路径上不能出现相邻的两个红色结点。
  5. 对于每个结点来说,从该结点到其子孙叶结点的所有路径上包含相同数目的黑结点。

红黑树的这5个性质中,第3点是比较难理解的,但它却非常有必要。我们看图1中的左边这张图,如果不使用黑哨兵,它完全满足红黑树性质,结点50到两个叶结点8和叶结点82路径上的黑色结点数都为2个。但如果加入黑哨兵后(如图1右图中的小黑圆点),叶结点的个数变为8个黑哨兵,根结点50到这8个叶结点路径上的黑高度就不一样了,所以它并不是一棵红黑树。


2. 基本操作

2.1 基本旋转操作:

(调节平衡时会用到)

右旋操作围绕4节点旋转,代码如下: void rotateRight(node *target){    //如上图4节点就是参数target     node *left=target->left;       //left节点是2节点     node *parent=target->parent;   //parentNode是target的父节点     if(parent!=NULL) {          left->parent= parent;      //如果父节点不为空,设置父节点的父子关系         if(parent->left==target)              parent->left=left;    //设置父节点到子节点的关系         else             parent->right=left;    //设置父节点到子节点的关系       }       node *move=left->right;       //move节点代表3节点      left->parent=target->parent;                                     target->parent=left;          //设置target节点到新父节点2的关系      left->right=target;           //设置left节点到target节点的关系      if(move!=NULL){               //设置move节点(3节点的父子关系)          target->left=move;        //target的左节点是3节点          move->parent=target;      //3节点的父节点target节点       }      if(target==root)         root=left;                 //如果旋转的节点是跟节点,需要更新跟节点引用 }

void rotateLeft(node *target){        //如上图5节点就是参数target    node * right =target->right;      //right节点是7节点    node *parent=target->parent;      //parentNode是target的父节点    if(parent!=NULL) {         right ->parent= parent;       //如果父节点不为空,设置父节点的父子关系        if(parent-> right ==target)             parent-> right = right;  //设置父节点到子节点的关系        else             parent->left= right;     //设置父节点到子节点的关系     }      node *move= right ->left;        //move节点代表7节点     right->parent=target->parent;     target->parent= right;           //设置target节点到新父节点7的关系     right ->left=target;             //设置left节点到target节点的关系     if(move!=NULL){                  //设置move节点(6节点的父子关系)        target->right=move;           //target的左节点是3节点        move->parent=target;          //6节点的父节点target节点      }     if(target==root)        root=right;                   //如果旋转的节点是跟节点,需要更新跟节点引用 }

2.2 求节点的后继节点:

(节点删除时会用到)

node* successor(node *target){   node* temp;   if(target->right!=NULL){ //case1 当target节点有右孩子时,返回右子树中最小的节点,即7节点                 temp=target->right;       while(temp->left!=NULL)         temp=temp->left;      return temp;   }   while(temp->parent!=NULL&&temp==temp->parent->right){    //case 2 当左子树为空时,可以理解为比7节点 小的,但是小节点中最大的节点,这个节点就应该是7节点的左子树中最大的节点,即6节点。      temp=temp->parent;   }   return temp->parent;}

3. 插入

红黑树的节点的插入过程,和普通的二叉查找树的插入过程类似。只是每个节点多了一个color域,代表节点的颜色(红色,黑色),新插入的节点的颜色是红色的。每个节点插入之后需要看一下当前插入节点的parent节点是否为红色,如果为黑色,则2叉树继续保持红黑树性质4,5,如果为红色,破坏了红黑树性质4,这时需要调整一下节点节点的颜色。所以当插入节点的父节点为红色时,插入后的节点调整需要分为3个case:

case1:第一种情况的条件是uncle节点不为空,并且uncle节点为红色节点。target节点是parent节点的左孩子或者右孩子,插入target节点之前,会保证数据结构中没有相邻的红色节点,且到叶子节点的黑色数目相同,这时插入target节点,只需要把parent节点,uncle节点变成黑色,grand节点变为红色即可,这样把grand节点的黑色下降到了孩子节点上(parent,uncle),保持了没有相邻的红色节点,且到叶子节点黑色数目相同,但是这样把grand节点变成了红色,可能会影响grand的父节点的红黑树性质(如果grand->parent节点为红色),所以需要把target指针指向grand节点,继续递归下去。

case2:是个过渡阶段,目的是让target节点为parent节点的左孩子,这样在后面的右旋时,target节点才不会成为grand的左孩子,正确的做法是交换target和parent节点,然后左旋target节点,令target指针指向parent节点,并对它进行左旋,进入case3,结果如右图(注,右图中target节点就是左图中parent节点,右图中parent节点就是左图中target节点)。反之如果在case2中直接右旋grand节点,(目的是保持没有相邻的红色节点,同时黑色节点数量保持一致)会出现下面几种情况:

第一种情况错误的旋转,交换parent节点和grand结果的颜色,显然这样的结果违反了不能出现两个连续的红色节点的性质

第二种情况错误的旋转,交换uncle节点和parent节点的颜色,同时uncle节点为红色,这样会导致uncle左右子树可能出现连续两个红色节点,剩下的错误旋转情况都是显而易见的,不是黑色节点的个数多了就是违反了红色节点不能相邻。

case3:情况是插入的target节点是parent节点左孩子,或是右孩子通过case2的操作变成了左孩子,这种情况直接右旋grand节点,并且交换parent节点和grand节点的颜色即可,这种情况不用在递归parent节点的上层数据结构了因为从grand节点的父节点看到的子节点就是黑色的,case3转换完毕后子节点还是黑色的,并且左右子树黑节点的数量维持不变,所以这种情况不用递归父节点的数据结构了。

插入过程的最后需要将root节点置为黑色,这是因为,case1中有可能grand节点就是root节点,case1的最后将root置为了红色,这时root节点没有父节点了,而需要保持红黑树的性质,需将root节点置为黑色。

node* insert(node *parent,int data){      if(parent->value>data){      //如果data比parent小,则插入parent的左子树          if(parent->left==NULL){  //为空直接插入节点              node* result=malloc(sizeof(node));//设置新节点和parent节点的关系              result->value=data;              result->parent=parent;              parent->left=result;              result->color=0;              insertAdjust(result); //新插入节点需要调整一下位置(case1,2,3)             return result;          }else{              return insert(parent->left,data);               }      }else{         if(parent->right==NULL){              node* result=malloc(sizeof(node));              result->value=data;             result->parent=parent;              parent->right=result;             result->color=0;             insertAdjust(result);              return;         } else{              return insert(parent->right,data);          }      } }
插入调整:void insertAdjust(node *insertNode){  node* temp=insertNode;  while(temp!=NULL&&temp!=root&&temp->parent->color==0){       //如果父节点为红色,就需要调整了     node *parent=temp->parent;     node *grandNode=temp->parent->parent;                                       //不需要判断grand节点是否为null,因为每次 置root为黑色了,如果parent为红色,必然有grand节点     if(parent==grandNode->left){               //case1         node *uncle=grandNode->right;          if(uncle&&uncle->color==0){                grandNode->color=0;                parent->color=1;                 uncle->color=1;                temp=grandNode;                 //递归grand节点之上的数据结构          }else{             if(temp==parent->right){           //case2                   temp=temp->parent;           //交换父节点和当前插入节点                   rotateLeft(temp);              }               temp->parent->color=1;           //parent节点置为黑色               temp->parent->parent->color=0;   //grand节点置为红色               rotateRight(temp->parent->parent);   //右旋grand节点               }        }      else{           node *uncle=grandNode->left;          if(uncle&&uncle->color==0){               grandNode->color=0;               parent->color=1;               grandNode->left->color=1;               temp=grandNode;          }else{              if(temp==parent->left){                  temp=temp->parent;                  rotateRight(temp);              }              temp->parent->color=1;              temp->parent->parent->color=0;              rotateLeft(temp->parent->parent);          }     }    root->color=1;   }}

4. 删除

相对于红黑树插入操作,删除操作复杂的多。

第一:先看最简单情况,即删除红色节点。删除红色节点,不影响红黑树平衡性质,如图:

只需要删除红色节点,不需要进行调整,因为不影响红黑树的性质。 黑色节点没有增多也没有减少。

注意:以下几种单支情况在平衡的红黑树中不可能出现。

因为上述的情况,红黑树处于不平衡状态。(破坏到null,黑色节点数目相同)
所以,平衡状态下红黑树要么单支黑-红,要么有两个子节点。

第二:删除单支黑节点

此种情况被包含在“第三”中,详见“第三”分析

第三:若删除节点有左右两个儿子,即左右子树,需要按照二叉搜索树的删除规律,从右子树中找最小的替换删除节点(该节点至多有一个右子树,无左子树),我们将该节点记为y, 将删除节点记为z,将y的右儿子记为x(可能为空)。
删除规则:用y替换z,交换y与z颜色,同时y = z,改变y的指向,让y指向最终删除节点。
为了便于理解,可以先这样假设:将y与z的数据交换,但颜色不交换,这样,实际相当于将删除转移到了y节点,而z处保持原先状态(处于平衡)。
此时可以完全不用了理会z节点,直接删除y节点即可。因为y最多只有一个右子树,无左子树,这便转移到了“第二”。

对于删除y节点,有几种考虑:
1. 若y为红色,则这种情况如上述”第一“所述,并不影响平衡性。(null视为黑色)
2. 若y为黑色,则删除y后,x替换了y的位置,这样x子树相对于兄弟节点w为根的子树少了一个黑节点,影响平衡,需要进行调整。


调整工作就是将x子树中找一合适红色节点,将其置黑,使得x子树与w子树达到平衡。

若x为红色,直接将x置为黑色,即可达到平衡;

若x为黑色,则分下列几种情况:

情况1:x的兄弟w为红色,则w的儿子必然全黑,w父亲p也为黑。

改变p与w的颜色,同时对p做一次左旋,这样就将情况1转变为情况2,3,4的一种。

情况2:x的兄弟w为黑色,x与w的父亲颜色可红可黑。

因为x子树相对于其兄弟w子树少一个黑色节点,可以将w置为红色,这样,x子树与w子树黑色节点一致,保持了平衡。
new x为x与w的父亲。new x相对于它的兄弟节点new w少一个黑色节点。如果new x为红色,则将new x置为黑,则整棵树平衡。否则,情况2转换为情况1,3,4 情况2转变为情况1,2,3,4.

情况3:w为黑色,w左孩子红色,右孩子黑色。

交换w与左孩子的颜色,对w进行右旋。转换为情况4

情况4:w为黑色,右孩子为红色。

交换w与父亲p颜色,同时对p做左旋。这样左边缺失的黑色就补回来了,同时,将w的右儿子置黑,这样左右都达到平衡。

看一下STL的红黑树删除调整操作:

if (y->color != __rb_tree_red) {       while (x != root && (x == 0 || x->color == __rb_tree_black))        if (x == x_parent->left) {          __rb_tree_node_base* w = x_parent->right;          //情况1          if (w->color == __rb_tree_red) {            w->color = __rb_tree_black;            x_parent->color = __rb_tree_red;            __rb_tree_rotate_left(x_parent, root);            w = x_parent->right;          }          //情况2          if ((w->left == 0 || w->left->color == __rb_tree_black) &&              (w->right == 0 || w->right->color == __rb_tree_black)) {            w->color = __rb_tree_red;            x = x_parent;            x_parent = x_parent->parent;          }           else           {            //情况3            if (w->right == 0 || w->right->color == __rb_tree_black) {              if (w->left) w->left->color = __rb_tree_black;              w->color = __rb_tree_red;              __rb_tree_rotate_right(w, root);              w = x_parent->right;            }            //情况4            w->color = x_parent->color;            x_parent->color = __rb_tree_black;            if (w->right) w->right->color = __rb_tree_black;            __rb_tree_rotate_left(x_parent, root);            break;          }        }        if (x) x->color = __rb_tree_black;      }  

只截取了平衡调整部分的代码,且省略在右侧删除的情况。


5. 代码

可以在poj3481测试正确性

#include<iostream>struct Key {    int value,num;};struct RBTNode {    Key key;    int lcount;    int rcount;    RBTNode* lchild;    RBTNode* rchild;    RBTNode* parent;    bool color;};class RBT {private:    const static bool RED = true;    const static bool BLACK = false;    RBTNode* m_null;    RBTNode* m_root;    void clear() {        RBTNode* p = m_root;        while (p != m_null) {            if (p->lchild != m_null) {                p = p->lchild;            }else if (p->rchild != m_null) {                p = p->rchild;            }else {                RBTNode* temp = p;                p = p->parent;                if (temp == p->lchild) {                    p->lchild = m_null;                }else {                    p->rchild = m_null;                }                delete temp;            }        }        m_root = m_null;    }    void delFixup(RBTNode* delNode) {        RBTNode* p = delNode;        while (p != m_root && p->color == BLACK) {            if (p == p->parent->lchild) {                RBTNode* sibling = p->parent->rchild;                if (sibling->color == RED) { //case1                    sibling->color = BLACK;                    p->parent->color = RED;                    leftRotate(p->parent);                    sibling = p->parent->rchild;                }                if (sibling->lchild->color == BLACK  //case2                    && sibling->rchild->color == BLACK                    ) {                        sibling->color = RED;                        p = p->parent;                }else {                    if (sibling->rchild->color == BLACK) {  //case3                        sibling->lchild->color = BLACK;                        sibling->color = RED;                        rightRotate(sibling);                        sibling = sibling->parent;                    }                    sibling->color = sibling->parent->color;  //case4                    sibling->parent->color = BLACK;                    sibling->rchild->color = BLACK;                    leftRotate(sibling->parent);                    p = m_root;                }            }else {                RBTNode* sibling = p->parent->lchild;                if (sibling->color == RED) {                    sibling->color = BLACK;                    p->parent->color = RED;                    rightRotate(p->parent);                    sibling = p->parent->lchild;                }                if (sibling->lchild->color == BLACK                    && sibling->rchild->color == BLACK                    ) {                        sibling->color = RED;                        p = p->parent;                }else {                    if (sibling->lchild->color == BLACK) {                        sibling->rchild->color = BLACK;                        sibling->color = RED;                        leftRotate(sibling);                        sibling = sibling->parent;                    }                    sibling->color = sibling->parent->color;                    sibling->parent->color = BLACK;                    sibling->lchild->color = BLACK;                    rightRotate(sibling->parent);                    p = m_root;                }            }        }        p->color = BLACK;    }    void insertFixup(RBTNode* insertNode) {        RBTNode* p = insertNode;        while (p->parent->color == RED) {            if (p->parent == p->parent->parent->lchild) {                RBTNode* uncle = p->parent->parent->rchild;                if (uncle->color == RED) {  //case1                    p->parent->color = BLACK;                    uncle->color = BLACK;                    p->parent->parent->color = RED;                    p = p->parent->parent;                }else {                    if (p == p->parent->rchild) {  //case2                        p = p->parent;                        leftRotate(p);                    }                    p->parent->color = BLACK;    //case3                    p->parent->parent->color = RED;                    rightRotate(p->parent->parent);                }            }else {                RBTNode* uncle = p->parent->parent->lchild;                if (uncle->color == RED) {                    p->parent->color = BLACK;                    uncle->color = BLACK;                    p->parent->parent->color = RED;                    p = p->parent->parent;                }else {                    if (p == p->parent->lchild) {                        p = p->parent;                        rightRotate(p);                    }                    p->parent->color = BLACK;                    p->parent->parent->color = RED;                    leftRotate(p->parent->parent);                }            }        }        m_root->color = BLACK;    }    inline int keyCmp(const Key& key1, const Key& key2) {        //比较两个Key的大小。这里可能有更复杂的比较,如字符串比较等。        return key1.value - key2.value;    }    inline void leftRotate(RBTNode* node) {        //把一个节点向左下方移一格,并让他原来的右子节点代替它的位置。        RBTNode* right = node->rchild;        node->rchild = right->lchild;        node->rcount = right->lcount;        node->rchild->parent = node;        right->parent = node->parent;        if (right->parent == m_null) {            m_root = right;        }else if (node == node->parent->lchild) {            node->parent->lchild = right;        }else {            node->parent->rchild = right;        }        right->lchild = node;        right->lcount += node->lcount + 1;        node->parent = right;    }    inline void rightRotate(RBTNode* node) {        //把一个节点向右下方移一格,并让他原来的左子节点代替它的位置。        RBTNode* left = node->lchild;        node->lchild = left->rchild;        node->lcount = left->rcount;        node->lchild->parent = node;        left->parent = node->parent;        if (left->parent == m_null) {            m_root = left;        }else if (node == node->parent->lchild) {            node->parent->lchild = left;        }else {            node->parent->rchild = left;        }        left->rchild = node;        left->rcount += node->rcount + 1;        node->parent = left;    }    RBTNode* treeMax(RBTNode* root) {//找到子树中最大的一个节点        RBTNode* result = root;        while (result->rchild != m_null) {            result = result->rchild;        }        return result;    }    RBTNode* treeMin(RBTNode* root) {//找到子树中最小的一个节点        RBTNode* result = root;        while (result->lchild != m_null) {            result = result->lchild;        }        return result;    }public:    RBT() {        m_null = new RBTNode;        m_null->color = BLACK;        m_null->lchild = m_null->rchild = m_null;        m_root = m_null;    }    ~RBT() {        clear();        delete m_null;    }    RBTNode* findBiggest()    {        RBTNode *p=m_root;        if (p==m_null)            return NULL;        else            return treeMax(m_root);    }    RBTNode* findSmallest()    {        RBTNode *p=m_root;        if (p==m_null)            return NULL;        else            return treeMin(m_root);    }    RBTNode* atIndex(int i) {//找到从小到大排序后下标为i的节点。i从0开始。        RBTNode* result = m_root;        if (i > result->lcount + result->rcount) {            result = NULL;        }else {            while (i != result->lcount) {                if (i < result->lcount) {                    result = result->lchild;                }else {                    i -= result->lcount + 1;                    result = result->rchild;                }            }        }        return result;    }    void del(RBTNode* node) {//删除一个节点        if (!node) return;        RBTNode* toDel = node;        if (node->lchild != m_null && node->rchild != m_null) {            toDel = treeNext(node);//找到中序后继:即右子树最左节点        }        RBTNode* temp = toDel;        while (temp->parent != m_null) {            if (temp == temp->parent->lchild) {                temp->parent->lcount--;            }else {                temp->parent->rcount--;            }            temp = temp->parent;        }        RBTNode* replace = toDel->lchild != m_null? toDel->lchild: toDel->rchild;        replace->parent = toDel->parent;        if (replace->parent == m_null) {            m_root = replace;        }else if (toDel == toDel->parent->lchild) {            replace->parent->lchild = replace;        }else {            replace->parent->rchild = replace;        }        if (toDel != node) {            node->key = toDel->key;        }        if (toDel->color == BLACK) {            //修改树,以保持平衡。            delFixup(replace);        }        delete toDel;    }    void insert(const Key& key) {//插入一个节点        RBTNode* node = new RBTNode;        node->key = key;        node->lcount = 0;        node->rcount = 0;        node->lchild = m_null;        node->rchild = m_null;        node->color = RED;        RBTNode* p = m_root;        RBTNode* leaf = m_null;        while (p != m_null) {            leaf = p;            if (keyCmp(node->key, p->key) < 0) {                p->lcount++;                p = p->lchild;            }else {                p->rcount++;                p = p->rchild;            }        }        node->parent = leaf;        if (leaf == m_null) {            m_root = node;        }else if (keyCmp(node->key, leaf->key) < 0) {            leaf->lchild = node;        }else {            leaf->rchild = node;        }        //修改树,以保持平衡。        insertFixup(node);    }    int nodeCount() {        return m_root != m_null? m_root->lcount + m_root->rcount + 1: 0;    }    RBTNode* search(const Key& key) {//按照key查找一个节点。        RBTNode* result = m_root;        while (result != m_null && keyCmp(key, result->key) != 0) {            result = keyCmp(key, result->key) < 0 ? result->lchild : result->rchild;        }        return result == m_null? NULL: result;    }    void toArray(int* array) {//把树中节点的值放进一个数组。        RBTNode* p = treeMin(m_root);        int i = 0;        while (p != m_null) {            array[i] = p->key.value;            i++;            p = treeNext(p);        }    }    RBTNode* treeNext(RBTNode* node) {//一个节点在中序遍列中的下一个节点。后继        RBTNode* result;        if (node->rchild != m_null) {            result = treeMin(node->rchild);        }else {            result = node->parent;            RBTNode* temp = node;            while (result != m_null && temp == result->rchild) {                temp = result;                result = result->parent;            }        }        return result;    }    RBTNode* treePre(RBTNode* node) {//一个节点在中序遍列中的前一个节点。前驱        RBTNode* result;        if (node->lchild != m_null) {            result = treeMax(node->lchild);        }else {            result = node->parent;            RBTNode* temp = node;            while (result != m_null && temp == result->lchild) {                temp = result;                result = result->parent;            }        }        return result;    }};int main(){    int no,k,p;    RBT r;    while (scanf("%d",&no),no){        Key key;        RBTNode *node;        switch (no){        case 1:            scanf("%d%d",&k,&p);            key.value=p;            key.num=k;            r.insert(key);            break;        case 2:            node=r.findBiggest();            printf("%d\n",node==NULL?0:node->key.num);            r.del(node);            break;        case 3:            node=r.findSmallest();            printf("%d\n",node==NULL?0:node->key.num);            r.del(node);            break;        }    }    //system("pause");    return 0;}

6. 个人总结

插入:

  1. 父亲黑色:不需调整,完成。
  2. 父亲红色,叔叔红色:父亲、叔叔、祖父换色,指向祖父,跳到1。
  3. 父亲红色,叔叔黑色:
    1. 是父亲的右儿子,左旋成左儿子,继续3.2
    2. 是父亲的左儿子,祖父右旋,父亲、祖父换色,完成。

删除:

  1. 有两个孩子:找后继,赋值,继续2
  2. 该点红色:删除,不需调整,完成。
  3. 该点黑色:删除及相关操作,指向它的唯一的孩子或者nil
    1. 该点红色:改成黑色,完成。
    2. 该点黑色:
      1. 兄弟红色:父亲、兄弟改色,左旋父亲,新兄弟一定为黑色,继续3.2.2
      2. 兄弟黑色:
        1. 兄弟带两个黑儿子:兄弟改成红色,指向父亲,跳到3.1
        2. 兄弟的右儿子为黑色,左儿子为红色:兄弟和其左儿子改色,右旋兄弟,此时新兄弟的右儿子一定为红色,继续3.2.2.3
        3. 兄弟的右儿子为红色,左儿子随意:交换兄弟和父亲的颜色,左旋父亲,完成。

0 0