The red and black trees in the introduction to algorithms are described as follows, which are similar to the four in STL source code analysis.
1. Each node is either red or black.
2. The root node is black.
3. Each leaf node (NiL) is black.
4. If a node is red, both of its sons are black.
5. For each node, all paths from the node to its child node contain the same number of black nodes.
To delete a node from the red-black tree, you can first use the common binary search tree method to delete the node from the red-black tree, and then restore the damaged red-black nature.
Let's recall the method of deleting a common Binary Tree node: Z points to the node to be deleted, and y points to the node to be deleted in the substantive structure. If the Z node has only one subnode or no subnode, then Y is the node pointing to Z. If the Z node has two subnodes, then y points to the successor node of the Z node (in fact, the same is true), and the successor node of Z cannot have the left subtree. Therefore, from the perspective of structure, a node that is actually deleted on a binary tree can only have one subtree at most.
Now let's look at the restoration process of the red and black nature:
If the node to which y points is a red node, after you delete y directly, the red and black nodes will not be damaged. The operation is complete.
If y points to a black node, several red and black nodes may be damaged. First, all the paths of the Y node are included. The black height is reduced by one (5th are damaged ). Second, if y has a red child node and Y has a red parent node, two adjacent red nodes (4th damaged) will appear after Y is deleted ). Finally, if y points to the root node and the child node of Y is red, after Y is deleted, the root node becomes red (2nd damaged ).
Among them, the destruction of 5th items made us quite uncomfortable. This affects the global environment. This action is too big and complicated. In addition, it is very difficult to restore other Redskins under this condition. So we should first solve this problem: If the black height of the path containing y is not changed, then the black height of other parts of the tree must be changed accordingly to adapt to it. Therefore, we can find a way to restore the black height of the original path containing y nodes. The practice is:Unconditionally push the black color of the Y node to its sub-node X.. (X may be a nil node ). In this way,X may have both black and red colors., That is, 1st items are damaged.
However, the 1st items are relatively easy to restore: 1. If X is both red and black, you can simply apply X to black. In this way, all problems are solved. It turns X into black, and 2 and 4 will be restored if there is a problem, and the algorithm ends. 2. If X is double black, we hope to push it up until it is pushed to the root node (adjust the tree structure and color, and X points to the new double black node, X keeps moving upwards), so that the root node has a dual black color. In this case, remove the black layer of X directly (because the root node is included in all the paths, in this way, all paths are black and high at the same time, reducing the number by one without damaging the red and black features ).
The following is a detailed analysis of how to restore the three red/black features that may be damaged: we know that if X points to a node, or, if X is the root node, you just need to make some changes to X. To operate other nodes except the x node, it must be like this: the x node is a double-layer black, and X has a parent node p. It is known that X must have a brother node W, and this w node must have two subnodes. (This is because the original tree meets the requirements of the black and red conditions. If the value of X is double black, there must be at least two nodes under the other subnode of P. Otherwise, the black height cannot be the same as that of the X path ). Therefore, we will analyze how these nodes are deformed and solve the problem within a relatively small scope. Another premise is that X must be the leaf node or nil node at the beginning of the tree. Therefore, in the recursive upward process, each step guarantees the next step at least
The sub-tree of X meets the requirements of the black and red features. Therefore, the sub-tree can be regarded as correct. In this way, the analysis is only limited to x nodes, X's parent node P and X's sibling node W, and w.
The following is just a case where X is black.
In this case, X should have double black at this time. The algorithm process is to move the extra heavy black up until a red node or root node is encountered.
Next, we will analyze four situations, which are actually eight, because four of them are symmetric, this can be distinguished by judging whether X is the right or left child of its parent node. Next, we will analyze the four situations in which X is the left child of its parent node. In fact, the next adjustment process is, it is to try to increase the number of black nodes on all paths passing through X by 1.
It is divided into the following four situations: (The following is a discussion of the case where X is the left son, And the right son is symmetric)
Case1: The brother w of X is red (try to change it to black)
Because W is red, the child node and parent node must be black. As long as the color of W and its parent node is switched, a left rotation is performed on the parent node, then, the left child node of W is placed on the brother node of X, and the brother node of X becomes black, and the red and black attributes remain unchanged. However, it is still not completed, but it is only a temporary transformation of Case 1 into the following situation 2, 3, or 4.
Case2: The Brother node W of X is black, and both child nodes of W are black. At this time, you can remove the heavy black of X and the black of W at the same time, and then add it to their parent node. This is where X points to its parent node, therefore, the parent node has a dual color. This heavy black node is moved up.
If the parent node is red and black, then the node X points to is red and black, and the X (that is, the parent node) is black. The problem has been completely solved.
If the parent node is a double-layered black node, the next recursion is performed with the parent node as the new x.
Case3: the sibling node W of X is black, and the left subnode of W is red, and the right subnode is black. In this case, by switching the color of W and Its left child node and performing a one-to-right rotation, you can switch to the fourth case below. Note that l is red, so the sub-node of l must be black, therefore, a sub-tree of node l in the rotation will be hung to the W-node after the red will not damage the red and black nature. The black height remains unchanged after deformation.
Case4: The Brother node W of X is black, and the right child node of W is red. In this case, after a left-hand operation, W is in the root position, and W is kept as the color of the original root position, at the same time, the color of the two new son nodes of W is changed to black, removing the heavy black of X. In this way, the entire problem is solved. Recursion ends. (In code, we point X to the root node to identify the end of recursion)
Therefore, as long as the above four cases are recursively processed, X always points to the root node or a red node, then we can end the recursion and solve the problem.
The above is the entire process of deleting nodes in the red and black trees.
Summary:
If we plot all the branches based on the above situation, we can draw the following conclusion:
Insert operation: Solves the red-red problem.
Delete operation: Black-black problems are solved.
That is, you can see from the branch graph that the situations that need to be traversed are red (INSERTED) or black (Deleted). If you carefully analyze and summarize all the situations, and stick to it, the red and black trees are not as horrible as imagined, and wonderful;
The code for deleting a node from the red/black tree is as follows:
# Include <iostream> using namespace STD; // define the node color Enum color {black = 0, red}; // red/black tree node typedef struct rb_tree_node {int key; struct rb_tree_node * left; struct rb_tree_node * right; struct rb_tree_node * parent; unsigned char rb_color;} rb_node; // red/black tree, contains a pointer typedef struct rbtree {rb_node * root;} * rb_tree; // static rb_tree_node nil = {0, 0, 0, 0, black}; # define pnil (& nil) // nil Node Address v Oid init_rbtree (rb_tree ptree) // initialize a red/black tree {ptree-> root = pnil;} // find the minimum key value node rb_node * rbtree_min (rb_node * proot) {While (pnil! = Proot-> left) {proot = proot-> left;} return proot ;} /* 15/\ 6 18/\ 3 7 17 20/\ 2 4 13/9 * // search specify the node's successor node rb_node * rbtree_successor (rb_node * proot) {If (pnil! = Proot-> right) // call the rbtree_min function {return rbtree_min (proot-> right) when finding the successor node in Figure 6;} // when the node does not have the right subtree, enter the following while loop (for example, when you find the successor node in Figure 13, its successor node is 15) rb_node * pparent = proot-> parent; while (pnil! = Pparent) & (proot = pparent-> right) {proot = pparent; pparent = proot-> parent;} return pparent ;} // Delete the rb_node * Delete (rb_tree ptree, rb_node * pdel) {rb_node * rel_delete_point; if (pdel-> left = pnil | pdel-> right = pnil) rel_delete_point = pdel; else rel_delete_point = rbtree_successor (pdel); // query the successor node rb_node * delete_point_child; if (rel_delete_point-> right! = Pnil) {delete_point_child = rel_delete_point-> right;} else if (rel_delete_point-> left! = Pnil) {delete_point_child = rel_delete_point-> left;} else {delete_point_child = pnil;} delete_point_child-> parent = rel_delete_point-> parent; If (rel_delete_point-> parent = pnil) // The deleted node is the root node {ptree-> root = delete_point_child;} else if (rel_delete_point = rel_delete_point-> parent-> right) {rel_delete_point-> parent-> right = delete_point_child;} else {rel_delete_point-> parent-> left = Delete _ Point_child;} If (pdel! = Rel_delete_point) {pdel-> key = rel_delete_point-> key;} If (rel_delete_point-> rb_color = black) {deletefixup (ptree, delete_point_child);} return rel_delete_point ;} /* The Introduction to algorithms is described as follows: RB-DELETE-FIXUP (t, x) 1 While X =root [T] and color [x] = Black 2 do if x = left [p [x] 3 then W else right [p [x] 4 if color [w] = Red 5 then color [w] dark black case 1 6 color [p [x] Random red case 1 7 left-rotate (t, P [x]) Case 1 8 W rows right [p [x] Case 1 9 If color [left [w] = black and color [right [w] = Black 10 then color [w] Then red case 2 11 x P [x] Case 2 12 else if color [right [w] = Black 13 then color [left [w] Then black case 3 14 color [W] Random red case 3 15 right-rotate (t, w) case 3 16 W outer right [p [x] Case 3 17 color [w] outer color [p [x] case 4 18 color [p [x] outer black case 4 19 color [right [w] random black case 4 20 left-rotate (t, P [x]) Case 4 21 x expose root [T] case 4 22 else (same as then clause with "right" and "Left" exchanged) 23 color [x] parse black * // the next step is very simple. That is, rewrite the pseudo code to the C ++ code to void deletefixup (rb_tree ptree, rb_node * node) {While (node! = Ptree-> root & node-> rb_color = black) {If (node = node-> parent-> left) {rb_node * brother = node-> parent-> right; if (brother-> rb_color = red) // Case 1: X's brother w is red. {Brother-> rb_color = black; node-> parent-> rb_color = red; rotateleft (node-> parent);} else // Scenario 2: x's brother w is black, {If (brother-> left-> rb_color = Black & Brother-> right-> rb_color = black) // both children of W are black {brother-> rb_color = red; node = node-> parent;} else {If (brother-> right-> rb_color = black) // Case 3: X's brother w is black, and W's right child is black (W's left child is red ). {Brother-> rb_color = red; brother-> left-> rb_color = black; rotateright (brother); brother = node-> parent-> right; // case 3 is converted to case 4} // case 4: X's brother w is black, the red brother-> rb_color = node-> parent-> rb_color; node-> parent-> rb_color = black; brother-> right-> rb_color = black; rotateleft (node-> parent); node = ptree-> root;} // else} else // same as above, the principle is the same, only when the left hand is changed to the right hand, and the right hand is changed to the left hand. Other codes remain unchanged. {Rb_node * brother = node-> parent-> left; If (brother-> rb_color = red) {brother-> rb_color = black; node-> parent-> rb_color = red; rotateright (node-> parent );} else {If (brother-> left-> rb_color = Black & Brother-> right-> rb_color = black) {brother-> rb_color = red; node = node-> parent;} else {If (brother-> left-> rb_color = black) {brother-> rb_color = red; brother-> right-> rb_color = black; rotateleft (brother); brother = node-> parent-> left; // case 3 is converted to case 4} Brother-> rb_color = node-> parent-> rb_color; node-> parent-> rb_color = black; brother-> left-> rb_color = black; rotateright (node-> parent); node = ptree-> root ;}}// while node-> rb_color = black; // If the x node is red, change it to black}