Red-black tree and deep understanding of C ++ implementation

Introduction

  Red-black tree is a special balanced binary tree (AVL), can be guaranteed in the worst case, the basic operation of the dynamic set time complexity is O (logn). So, it is widely used in the development of enterprise.

Nature of the red-black tree

  In a red-black tree, adding a memory bit (attributes color) to represent the color of each node on the node, and only the color is red or black. By color of each node on any simple path from the root to a leaf bound, a red-black tree path will ensure that no more than 2 times grow other paths, thus approximately balanced.

  Each node in the tree comprises five attributes: color, val, lchild, rchild and p (optional). If a node has no child node, or a parent node, then the node pointer corresponding attribute value NIL. We can put these NIL seen as a pointer to the binary search tree leaf node (external node), and the junction with the keyword as internal nodes of the tree.

  A red-black tree is red and black to meet the following property binary search tree:

  • Each node, or red, or black.
  • Root is black.
  • Each leaf node (NIL) is black.
  • If a node is red, then its two child nodes are black.
  • For each node, a simple path from the node to which all descendant leaf nodes, contain the same number of black nodes.

  To facilitate processing of the boundary condition red-black tree code, using a sentinel to represent NIL. For a red-black tree, tree, Sentinel NIL is a common attribute with a node in the tree common objects. Its color attribute is black, other attributes can be any value.

Rotation

  In a red-black tree containing n keywords, insert and delete operations, required time complexity is O (logn), since these two operations, results in a tree is not satisfied after the red-black tree insertion and deletion nature. In order to maintain these properties, it is necessary to change the color and some pointers tree nodes.

  Pointer to a structure modification is accomplished by the rotation, which is a binary search tree to maintain the nature of the local search tree operation, it is divided into left and right hand rotation. As shown below:

  

  Code shows left and right hand of the following operations:

 1 template<typename T>
 2 void RedBlackTree<T>::LeftRotation(RedBlackNode<T>* &t){
 3     RedBlackNode<T> *temp = t->rchild;
 4     t->rchild = temp->lchild;
 5     if(Parent(t)==NIL){
 6         root = temp;
 7     }
 8     temp->lchild = t;
 9     Parent(t)->rchild = temp;
10 }
11 
12 template<typename T>
13 void RedBlackTree<T>::RightRotation(RedBlackNode<T>* &t){
14     RedBlackNode<T> *temp = t->lchild;
15     t->lchild = temp->rchild;
16     if(Parent(t)==NIL){
17         root = temp;
18     }
19     temp->rchild = t;
20     Parent(t)->lchild = temp;
21 }

  Both left and right pseudo code can refer to "Introduction to Algorithms" (Book bricks).

Red-black tree insert

  As mentioned above, in a red-black tree with n keywords, insertion operation takes time complexity is O (logn). To do this, need to insert a node into original red red-black tree. So the question is, why is inserted red node, rather than black nodes it? We know that there are five red-black tree properties, if inserted red node, it may violate property 4, and if it is inserted into the black node, it must violate nature 5. That is, the insertion node red black than the insertion node is less likely to violate the nature of red-black tree, while the number of violations of the nature more, the more the corresponding adjustment operation is done, resulting in time complexity is also higher, thus affecting the execution speed of the algorithm. "Data structure and algorithm analysis" (where a significantly higher, Tsinghua University Press), a book, gives the insertion node and the insertion node is red and black for the operation of such algorithm is the insertion node is red to explain.

  For a red-black tree, a node is inserted into a red, there are six cases, three of which are symmetrical with the other three. This depends on the insertion node z father node is inserted into the node grandfather node left child or right child.

  The following three cases are given both symmetry, corresponding to:

  • case1: the insertion junction node y t z is red.

  

  The figure shows the case of this situation, the insertion node that case it is the parent node zp z and y are nodes which t occurs when red. Because the insertion junction node zpp z grandfather is black, it will zp and y are both colored black, red problems are solved and zp z, and because the nature of claim 5, if only the color and y zp to black, the nature of which will be destroyed, so the need for color zpp node is adjusted to ensure compliance with the nature of 5.

  However, after adjustment Property 1, it will be able to maintain the nature of the red-black tree do? We newly inserted node at z, z 'denotes the node through the operation by the operator can know, z' = zpp. Then, after this, with the following results:

    •  Because this operation the zpp colored red, the node z 'at the start of the next operation is red.
    •     In this operation a node z'.p zppp, and this does not change the color of the node. If it is the root, before this iteration it is black, and then it either at the beginning of the next operation is black.
    •  We also know, case1 retention properties 5, but it will not cause damage to property or properties 1 of 3.

  If the node z 'at the start of the next operation is the root, then in the correction operation case1 unique properties destroyed 4. Since z 'is the root and red, so the only properties Property 2 become breached, which is the z' caused.

  If the node z 'at the start of the next operation is not the root node, the nature of the case1 2 does not cause destruction, case1 corrected at the start of the operation of the properties unique 4 violations. Then z 'z'.p same colored red and, therefore, if z'.p is black, there is no violation of the nature 4, if z'.p is red, the nature of the violation 4.

  • case2: insertion junction node y t z is black and z is a right child.
  • case3: insertion junction node y t z is black and z is a left child.

  In case3 and case2, z is t-junction is black. By zp z is left child or right child to distinguish between the two cases (tert nodes are black, can not logically be distinguished). For both cases, as shown below:

      At left case2, the right is case3

  We found that some relationship exists case2 pointer structure and case3, it is clear that can be converted to each other by left and right hand operation therebetween. Since zp z are red and, therefore, the rotating operation and the properties of the black high trees 5 have no influence. No matter what how to enter both cases, y is always black, or we need to perform the corresponding operation case1. In addition, the rotation operation, has the advantage that does not change the rotation, z identity, even though lead to z of about child identity has changed, but still zp children. In case3, we can change the color of certain nodes, and make a right-handed, can guarantee 5 properties. Thus, since there is no longer a row of two red nodes, therefore, to ensure that the properties of the red-black tree, all of this processing is also finished. As follows:

  Can be seen, and Case2 case3 operation, so that ultimately the node tree after insertion, red-black tree sustaining properties. Thus, it can not help but suspect that this operation can fully guarantee? The answer is yes. To prove the following:

  • case2 let z pointing red zp. In the case3 and case2, z, z or color does not change, therefore, after the case2 into case3, it does not produce a change in other properties.
  • the zp case3 colored black, so that if the next operation is started zp is the root, it is black.
  • And case1 as red-black tree properties are maintained at 1,3,5 and case3 in case2.

  Since the node z in the case3 case2 and not the root node, and therefore, properties of 2 is not damaged, and therefore both cases are not caused by the nature of the violation 2. Thus, to prove the nature zp zpp children left when the red-black tree after the insertion of z, according to the above adjustment can be done to restore the red-black tree. When zp is the right child zpp, since a large front case is symmetrical, therefore, by modifying the corresponding left and right, and can be achieved. The full realization of tree responses, can be maintained by the while loop. The following is the code that implements the tree inserted:

  1 template<typename T>
  2 bool RedBlackTree<T>::Insert(T e){
  3     RedBlackNode<T> *p, *f;
  4     p = f = NULL;
  5 
  6     if(!searchBST(root, p, e, f)){//not found, need to create, p points to the last node.
  7 
  8         RedBlackNode<T> *s = createNewNode(e);
  9         if(root==NULL){
 10             root = s;
 11             root->color = "black";
 12         }
 13         else{
 14             if(e<p->val){
 15                 p->lchild = s;
 16             }
 17             else{
 18                 p->rchild = s;
 19             }
 20             if(p->color == "red"){//double red node, need to adjust
 21                 adjustDoubleRed(s, p);
 22             }
 23         }
 24         return true;
 25     }
 26     else{//node exists. return false
 27         return false;
 28     }
 29 }
 30 
 31 template<typename T>
 32 RedBlackNode<T>* RedBlackTree<T>::Parent(RedBlackNode<T>* &t)const{
 33     /*
 34     *@Parameter:
 35     *q: a queue to save rb-node.
 36     *t: a point which points to a node in the RBTree.
 37     *father: a point which points to the father node of t.
 38     */
 39     queue<RedBlackNode<T>*> q;
 40     RedBlackNode<T>* father;
 41     if(root!=NULL){
 42         q.push(root);
 43         while(!q.empty()){//BFSTraverse to find the father node of t.
 44             father = q.front();
 45             q.pop();
 46             if((father->lchild!=NIL&&father->lchild==t)||(father->rchild!=NIL&&father->rchild==t)){
 47                 return father;
 48             }
 49             else{
 50                 if(father->lchild!=NIL){
 51                     q.push(father->lchild);
 52                 }
 53                 if(father->rchild!=NIL){
 54                     q.push(father->rchild);
 55                 }
 56             }
 57         }
 58     }
 59     return NIL;        //not found, return NIL
 60 }
 61 
 62 template<typename T>
 63 bool RedBlackTree<T>::searchBST(RedBlackNode<T>* &t, RedBlackNode<T>* &p, T &e, RedBlackNode<T>* f)const{
 64     //在树中t中递归地查找其值等于e的数据,若查找成功,则指针p指向该数据
 65     //结点,并返回true,否则指针p指向查找路径上访问的最后一个结点以便插入
 66     //并返回false,指针f指向p的双亲,其初始调用值为NULL。Insert()调用
 67     if(t==NULL||t==NIL){
 68         p = f;
 69         return false;
 70     }
 71     if(e==t->val){
 72         p = t;
 73         return true;
 74     }
 75     else if(e<t->val){
 76         return searchBST(t->lchild, p, e, t);
 77     }
 78     else{
 79         return searchBST(t->rchild, p, e, t);
 80     }
 81 }
 82 
 83 template<typename T>
 84 void RedBlackTree<T>::LeftRotation(RedBlackNode<T>* &t){
 85     RedBlackNode<T> *temp = t->rchild;
 86     t->rchild = temp->lchild;
 87     if(Parent(t)==NIL){
 88         root = temp;
 89     }
 90     temp->lchild = t;
 91     Parent(t)->rchild = temp;
 92 }
 93 
 94 template<typename T>
 95 void RedBlackTree<T>::RightRotation(RedBlackNode<T>* &t){
 96     RedBlackNode<T> *temp = t->lchild;
 97     t->lchild = temp->rchild;
 98     if(Parent(t)==NIL){
 99         root = temp;
100     }
101     temp->rchild = t;
102     Parent(t)->lchild = temp;
103 }
104 
105 template<typename T>
106 void RedBlackTree<T>::adjustDoubleRed(RedBlackNode<T>* &s, RedBlackNode<T>* &p){
107     /*
108     *@Parameter:
109     *s: rb-node.
110     *p: the father node of s.
111     */
112     RedBlackNode<T> *y, *gp;
113     while(p->color=="red"){
114         gp = Parent(p);
115         if(p==gp->lchild){
116             y = gp->rchild;
117             if(y->color=="red"){//case 1
118                 p->color = "black";
119                 y->color = "black";
120                 gp->color = "red";
121                 s = gp;
122                 p = Parent(s);
123             }
124             else if(s==p->rchild){//case 2
125                 s = p;
126                 LeftRotation(p);
127             }
128             else{
129                 p->color = "black";
130                 gp->color = "red";
131                 RightRotation(gp);
132             }
133         }
134         else{
135             y = gp->lchild;
136             if(y->color=="red"){//case 1
137                 p->color = "black";
138                 y->color = "black";
139                 gp->color = "red";
140                 s = gp;
141                 p = Parent(s);
142             }
143             else if(s==p->lchild){//case 2
144                 s = p;
145                 RightRotation(s);
146             }
147             else{
148                 p->color = "black";
149                 gp->color = "red";
150                 LeftRotation(gp);
151             }
152         }
153     }
154     root->color = "black";
155 }

代码的操作,与前文图片所描述的操作相一致。

 

 红黑树的删除操作

  由于红黑树与BST树相似,因此,其删除操作与BST树在逻辑上是基本一致的,唯一的区别在于,红黑树需要对删除结点后的树进行调整,使其符合红黑树的性质。对于一棵红黑树来说,如果先不考虑结点的颜色,删除一个结点无非是三种情况,这一点与BST树是一致的,即:

  • 被删除结点没有左右子结点;
  • 被删除结点仅有一个子节点(左或右都有可能);
  • 被删除结点左右子结点都存在;

  根据上述三种情况,可以编写出BST树的删除结点操作的代码,下面给出BST树的删除操作示意图:

  

  很明显,红黑树在结点的结构上,也是符合上述形式的,即左<根<右,因此,红黑树的删除操作是从BST输的删除操作的基础上,修改得到的,为什么需要修改呢?就是因为红黑树的每个结点具有红黑属性。

  由于红黑属性的影响,导致,删除结点后红黑树将不符合红黑树原有的特性,我们知道,删除某个结点,按照上述调整,将会使得被删除结点所在的子树不符合原红黑树的特性1、2、4或5(非删除结点不受影响)。因此,只需要对子树进行颜色调整,就能使红黑树性质保持不变。

伪码中Transplant函数的实现

  如何删除的原理已经讲明白了,那么我们看,两个结点是如何替换(也就是发生删除操作的)。

  

  在伪码中,结点u为被替换结点,你可以理解为,被删除结点,而v是用来替换被删除结点的结点(通常为u的子节点或者u的右子树结点的最小节点)。

  下面是我实现的transplant函数:

 1 template<typename T>
 2 void RedBlackTree<T>::Transplant(RedBlackNode<T>* &u, RedBlackNode<T>* &v){
 3     /*  4  *a function to achieve node u is replaced by v.  5  *@Parameter:  6  *u: a node which is replaced by v.  7  *v: a node wants to replace u.  8 */  9 if(Parent(u) == NIL){//待删除结点为根结点. 10 root = v; 11  } 12 else if(u==Parent(u)->lchild){ 13 Parent(u)->lchild = v; 14  } 15 else{ 16 Parent(u)->rchild = v; 17  } 18 }

 红黑树删除操作的具体实现

  下面给出删除操作的伪代码(源自《算法导论》)。

  在上述代码中,结点z为删除结点,y为指向结点z的指针。我们知道,BST的删除操作是很容易实现的,对于红黑树来说,关键在于,删除操作以后,什么情况下,会破坏红黑树的红黑性质。

  由于y的颜色有可能发生改变(因为根据代码,y始终指向树结构中被删除结点的位置),用变量y_original_color存储了发生改变前的y位置的颜色。第2行和第10行在给y赋值之后,立即设置该变量。当z有两个子结点时,则y!=z且结点y移至红黑树中结点z的原始位置;第20行给y赋予和z一样的颜色。然后保存y的原始颜色,以在删除操作结束时,测试它;如果它是黑色的,那么删除或移动y会引起红黑性质的破坏,为什么会是黑色引起红黑性质的破坏呢?

  •   对于情况1,即不存在子结点的被删除结点来说,什么情况下删除该结点以后会改变原有红黑树的性质呢?很显然,被删除结点是黑色的时候,删除它会违背红黑树的性质5,而被删除结点为红色的时候,删除它并不会影响红黑树的性质,直接修改即可(如下所示),在这里,就产生了删除结点后,后续修改颜色的第一种情况。

  如上图所示,如果删除结点5,对于左侧的树,如果删除结点y,则结点7不会违反任何红黑树的性质,因为结点y的子结点为必定为黑色(由于红黑树的性质),因此,y为红色不会引起红黑树性质的改变;对于右侧的树,如果删除结点y,则如果结点y的子结点为NIL(黑色),不会引起结点7与结点y子结点之间都为红色,从而不违反了红黑树的性质。

  •   对于情况2,即存在左结点或者右结点,由于红黑树结点存在color属性,因此,常见的做法是将用来替换删除结点的结点的颜色改成与删除结点颜色一致,这样,只需要对修改过指针结构后的子树进行修改其颜色,即可完成红黑树性质的保持。那么,由于颜色的存在,又会有那些情况的出现呢?我们知道,一个结点无非是红色或者黑色,且根据性质,红结点的子结点颜色必为黑色,那么,以被删除结点为左结点为例,有下面两种情况:

   

  从左边的红黑树来看,如果待删除结点颜色为黑色,当对该结点进行操作时,则由于,其子结点为红色,与y结点的父结点同为红色,因此,会违背红黑树的性质,而如果是右边的情况,则不会,因为删除结点y以后,由于结点4依旧为黑色,不会破坏红黑树的性质。对于这种情况下,左子树结点不存在而右子树结点存在的情况,也是同样的道理,读者可以自己画图思考一下。

 

  •   对于情况3,即被删除结点同时存在左右子结点,如下图所示:

  从上图来看,如果删除结点5,会与情况2一样而违反性质,而对于右边的树,则不会,因为,我们删除的方式,是将z结点也就是结点5的左子树,连接到结点5右子树的值最小的结点上。然后用这个最小结点来替换原来结点z(也就是图上结点5的位置)。这样做的好处是,仍可以保证删除后的树仍满足BST树的性质,我们只需要对被删除结点的子树进行修改颜色性质就可以了,而且,不论最小结点的颜色如何,都不会导致出现两个红结点的情况。这一点可能很多人会存在疑惑,我们来分析一下:

  对于左右子树都存在的的删除结点来说,此时,y从指向z转向了指向删除结点z的右子树种最小的一个结点(右子树的最左结点),这样的指向,无非两种情况,一种是右子树的最左结点就是被删除结点z的右孩子,即其右孩子的左孩子为NIL,这样,以上右图为例,y指向了结点6,然6后,用y_original_color来保存结点6的颜色,用x指向6的右孩子,由于在这里,y的父亲结点依旧为删除结点z,因此,设置好结点属性x.p = y,然后,执行替换操作(Transplant)来实现删除的目的,可以看到,transplant操作是对删除结点z和替换结点y进行操作的,对于这种情况来说,是将z与其右孩子进行替换,根据伪码,结点7的左孩子指向了结点6。然后结点6也就是现在的结点y的左孩子指向了结点z的左孩子,这样就完成了然后将结点y的颜色,改成结点z的颜色,为什么这么做呢,是为了保持与原来红黑树相同的特性,因为我们知道,在删除结点5之前,结点5左右两棵子树的一条路径上的黑色结点数目是相同的,但是,由于结点6的上位(替换了其父结点),而父结点的左孩子直接成为其左子树,这就导致了左右两子树的不平衡,调整为与z结点相同的颜色以后,可以使得对红黑树修改操作仅局限于结点y这棵子树中进行。

  另一种情况则是右子树的最左结点不是被删除结点z的右孩子,即其右孩子的左孩子非空,那么,其余操作不变,比较巧妙的是,这里运用了最左结点的左孩子为NIL的特性,将最左结点的右子树接到了其父节点的左边(这就保证了BST树的有序性),且这样的做法,使得该结点与树在结构上断开,无法通过树的根结点访问到,因此,后续再执行一次删除结点z与结点y的替换操作,就可以完成这样的删除操作。这种情况下,会产生y为红色或者黑色结点的问题,同样,也只会有黑色会违背红黑树的性质。

 红黑树删除操作后的修改结点颜色操作

  通过上面的分析,我们很容易得到这样的结论,那就是,只有当y为黑色结点的时候,才会发生违背红黑树性质的情况,因此,需要对这样的情况进行修改,来保持红黑树的性质。下面给出《算法导论》中,关于该操作的伪代码:

  

  在分析源码以前,我们首先来分析,执行删除操作以后,会出现哪些违背红黑树性质的情况。在这个操作中,结合删除操作的代码,我们可以发现,x始终指向具有双重黑色的非根结点。那么,就会有4种情况,这四种情况与插入操作中的是类似的,请读者结合上述删除操作,自行分析。 

 下面附上红黑树删除操作的代码。

 1 template<typename T>
 2 void RedBlackTree<T>::Delete(RedBlackNode<T>* &t){
 3     /*
 4     *function to delete node t in redblacktree.
 5     *@Parameter:
 6     *t: a node need to be deleted.
 7     */
 8     RedBlackNode<T> *y;
 9     RedBlackNode<T> *p;
10     y = t;
11     string y_original_color = y->color;
12     if(t->lchild==NIL){
13         p = t->rchild;
14         Transplant(t, t->rchild);
15     }
16     else if(t->rchild==NIL){
17         p = t->lchild;
18         Transplant(t, t->lchild);
19     }
20     else{
21         y = TreeMinimum(t->rchild);
22         y_original_color = y->color;
23         p = y->rchild;
24         if(Parent(y)!=t){
25             Transplant(y, y->rchild);
26             y->rchild = t->rchild;
27         }
28         Transplant(t, y);
29         y->lchild = t->lchild;
30         y->color = t->color;
31     }
32 
33     if(y_original_color=="black"){
34         RBDeleteFixup(p);
35     }
36     delete t;
37     t = NULL;
38 }

红黑树删除后,修改颜色的操作代码。

template<typename T>
RedBlackNode<T>* RedBlackTree<T>::TreeMinimum(RedBlackNode<T>* t){
    RedBlackNode<T> *p;
    while(t!=NIL){
        p = t;
        t = t->lchild;
    }
    return p;
}

template<typename T>
void RedBlackTree<T>::RBDeleteFixup(RedBlackNode<T>* &t){
    RedBlackNode<T> *p;
    RedBlackNode<T> *f;
    while(t!=NIL&&t->color=="black"){
        if(t==Parent(t)->lchild){
            p = Parent(t)->rchild;
            if(p->color=="red"){
                p->color = "black";
                Parent(t)->color = "red";
                f = Parent(t);
                LeftRotation(f);
                p = Parent(t)->rchild;
            }
            if(p->lchild->color=="black"&&p->rchild->color=="black"){
                p->color = "red";
                t = Parent(t);
            }
            else if(p->rchild->color=="black"){
                p->lchild->color = "black";
                p->color = "red";
                RightRotation(p);
                p = Parent(t)->rchild;
            }
            else{
                p->color = Parent(t)->color;
                Parent(t)->color = "black";
                p->rchild->color = "black";
                f = Parent(t);
                LeftRotation(f);
                t = root;
            }
        }
        else{
            p = Parent(t)->lchild;
            if(p->color=="red"){
                p->color = "black";
                Parent(t)->color = "red";
                f = Parent(t);
                RightRotation(f);
                p = Parent(t)->lchild;
            }
            if(p->rchild->color=="black"&&p->lchild->color=="black"){
                p->color = "red";
                t = Parent(t);
            }
            else if(p->lchild->color=="black"){
                p->rchild->color = "black";
                p->color = "red";
                LeftRotation(p);
                p = Parent(t)->lchild;
            }
            else{
                p->color = Parent(t)->color;
                Parent(t)->color = "black";
                p->lchild->color = "black";
                f = Parent(t);
                RightRotation(f);
                t = root;
            }
        }
    }
    t->color = "black";
}

 

 

 

 

  

 

 

 

 

 

 

 

  

  

  

  

  

Guess you like

Origin www.cnblogs.com/sgatbl/p/9484097.html